mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-30 15:10:40 +00:00
Feature: Websocket order handling (#446)
* Initial changes, removing exchange name as an arg and puts it in the pointer struct. Adds case to ws routines * Adds CancelAllOrders func, adds GetByExchangeAndID. Adds modify handler in routines.go * initial poor attempts to have bitmex work with new datahandler handlers. fixes ordersides * bitmex Completes new order * Better bitmex handling, but not complete. Begins a gargantuan task of unifying order data structs. Sometimes an order update will contain lot's of information, so its best to be able to update all fields of our orders, rather than just an arbitrary subset. As a result, everything will be broken for the foreseeable future :glitch_crab: * Removes old order handler which did nothing. Updates order properties for everything everywhere - now consistent. Changes order status. Adds asset type and wallet address to all order types * Adds order updater to update only relevant fields since the object is generic, we don't know what fields are passed from what exchanges. Adds "lastupdated" field to order.Detail. Expands order cancellation for engine orders. * Ensures that new orders are added to the ordermanager's order store. Saaa many comments. Internalises orderStore get func. Adds internalOrderID to orderdetail and adds websocket support for it * Fixes a cancelAllOrders oopsie doopsie * Adds potential func to update orderdetails from an orderdetail struct. Unsure if will keep. * Begins btcmarkets implementation. Expands order "stringToOrder" funcs to allow for some more flexible string coversions. Removes order.Submit via websocket as it would cause unlimited order place issues :D * Finishes btc markets without testing * Adds untested ws auth func to btse * Finises btse, fixes btcmarkets bug * Adds coinbasepro support * Fixes a few more fields in coinbase pro and readds the extra subs * Begins work on coinbene. Plus theyve added a new ws connection yeee * Wasted a bunch of time adding support to an additional websocket that isn't needed ;_; Fixed a bug in coinbasepro. Fully kitted out coinbene support. Updates order types with all fields * Removes extra websocket connection ;_; * Finishes gemini. Fixes order side unknown * Adds okgroup support. Moves byte reading to another function to allow for unit testing. Updates routines to use pointers. Updates date update handling for order details * Finishes order data for okgroup websocket, but starts the STRANGE process of converting all other websocket endpoints to be a little less silly * Cleans up okroup websocket implementation. Fixes bug in Gemini * Adds poloniex support. Updates ws order handling * new bitmex support. Adds some tests now that its all in its own func. Fixes poloniex bug * Begins work on authenticated binance websocket * Attempts to track user data via binance websocket * Maybe finishes Binance websocket support * Begins adding test coverage to orders.go. Updates names of script properties to match updated * Begins an experiment with code coverage. Fixes more rebase issues * Completes orders coverage. Botches a few other things though. Fixes more scripting stuff * All tests in engine package pass * Adds some loevely routine tests * Moves ordermanager to test Bot ordermanager Adds lovely routine tests to ensure things that get sent to be handled the data handler are handled by the data handler by handling them * Replaces "wsHandleData" with "wsReadData" as that's what its going to do now. * Splits all wsHandleData into wsReadData and wsHandleData to allow for easy testing via sending []byte json examples to test proper functionality. Breaks so many tests * Fixes majority of test issues. But data races which are tough on the engine package * "Fixes" test by removing shutdown test. It interferes with too many things. Requires some thought * Tests all the binance websocket points * Adds better bitfinex websocket support. * Adds testing for bitfinex, bitstamp and btcmarkets. Fixes websocket bugs encountered * Adds BTSE ws tests. Fixes bugs in ws * Adds coinbase pro tests. Fixes any issues * Coinbene tests * Starts to handle coinut. Runs into a problem conceptually regarding websocket roundtrip and orders. Both events need to happen without impacting eachother/racing * Addresses a data race issue regarding websocket and bot order management submission - order submission locks at an earlier point to prevent routines.go from creating an order before order submission creates it. Updates rpcserver to use order management bot to submit orders. * Finishes the hectic coinut testing * Adds tests for gateio * Fixes rebase issues. Updates tests to work without being overloaded * Begins testing of gemini. fixes up minor issues * ginishes gemini tests and fixes * Adds hitbtc tests. Fixes all the many issues with hitbtc websocket * Adds remaining tests. Increases default test channel limit again * Begins work towards huobi tests * Finishes huobi tests * Fixed all mythical rebase adventures * Begins kraken transformation * Finishes kraken. Fixes coinbene leverage now that its changed * Begins okgroup testing * Adds okgroup ws tests * Does some poloniex * Fixes basic curreny issue by extracting to func * Begins redesign of poloniex websocket datahandling. Completes authenticated handling, now onto unauth * Finishes poloniex revision * Finishes ZB additions * Fixes data races * Fixes rebase issues. Fixes bad kraken logic * Fixes after reviewing code * lint everywhere * Fixes lingering lints * lint * Adds test coverage to order detail and modify updating * Fixes linting * Fixes huge int, fixes date tests * Adds GetByExchange, adds test for it. Protects fakepass echange. Renames DisplayQty to DisplayQuantity. Removes verbose. Adds some websocket properties. Updates bitmex asset type in test * Addresses timestamps, type abbreviations, verbosity. Expands binance kline switch cases. Updates some websocket capabilities. * Adds coverage to the stringToOrderType/Status functions introduced in PR * Minor fixes addressing some time, error text and use of StringDataCompareInsensitive * Introduces shiny new system which checks if there is an awaiting ID, if found, processes via wrapper method, else, goes through wsHandleData method. Removes weird locking system from wrapper/websocket data race. Updates bitfinex to properly handle websocket order requests and notifications * Moves fakePassingExchange to test_helper. Fixes some order side implementations for trades. Botches a new error type * Adds new error type to track and handle order classification errors separately * Fully fleshes out ClassificationError for all instances of status conversion. Even in order trades and some wrapper functions * Introduces common.SimpleTimeFormat for "2006-01-02 15:04:05". Fixes binance and bitfinex issues with auth endpoint use, map casting. Expands more order.ClassificationError usage. Fixes some more generic websocket response errors * Future proofs order updating by utilising asset types. Expands testing to accomodate. Adds shiny new time type. Expands wrapper websocket functionality definitions * minty linty * Broken end of day code addressing basic nits on comments, returns and currency conversion * Adds testing to btcmarkets websocket. Also updates websocket orderbook to use update instead * Fixes fun rebase fun fun so fun * Addresses minor nits regarding changed interface and comments * Creates new function `GetRequestFormattedPairAndAssetType` to retrieve a currency pair and asset type based on a string. It will iterate over enabled pairs and compare them to formatted pairs and then return that pair if found. * Fixes test * Adds a single line to the end of the file, because that would be really bad if it wasn't there * Updates fakepassexchange to not use params, updates test params, uses fatal in some tests where its important, updates order manager to have a rwmutex, removes some returns, improves ws key test for binance, updates properties to reflect their actual values, adds some more websocket properties * Addresses binance switch linting * Updates leverage property to int64 * Fixes what was broken
This commit is contained in:
@@ -439,7 +439,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := a.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -453,7 +453,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := a.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -479,11 +479,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
|
||||
response, err := a.SubmitOrder(orderSubmission)
|
||||
@@ -507,10 +507,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.BTC, currency.LTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := a.CancelOrder(orderCancellation)
|
||||
@@ -530,10 +530,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.BTC, currency.LTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := a.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -225,8 +225,8 @@ func (a *Alphapoint) SubmitOrder(s *order.Submit) (order.SubmitResponse, error)
|
||||
}
|
||||
|
||||
response, err := a.CreateOrder(s.Pair.String(),
|
||||
s.OrderSide.String(),
|
||||
s.OrderSide.String(),
|
||||
s.Side.String(),
|
||||
s.Type.String(),
|
||||
s.Amount,
|
||||
s.Price)
|
||||
if err != nil {
|
||||
@@ -235,7 +235,7 @@ func (a *Alphapoint) SubmitOrder(s *order.Submit) (order.SubmitResponse, error)
|
||||
if response > 0 {
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(response, 10)
|
||||
}
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
@@ -251,7 +251,7 @@ func (a *Alphapoint) ModifyOrder(_ *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (a *Alphapoint) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -348,19 +348,19 @@ func (a *Alphapoint) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detai
|
||||
RemainingAmount: resp[x].OpenOrders[y].QtyRemaining,
|
||||
}
|
||||
|
||||
orderDetail.OrderSide = orderSideMap[resp[x].OpenOrders[y].Side]
|
||||
orderDetail.OrderDate = time.Unix(resp[x].OpenOrders[y].ReceiveTime, 0)
|
||||
orderDetail.OrderType = orderTypeMap[resp[x].OpenOrders[y].OrderType]
|
||||
if orderDetail.OrderType == "" {
|
||||
orderDetail.OrderType = order.Unknown
|
||||
orderDetail.Side = orderSideMap[resp[x].OpenOrders[y].Side]
|
||||
orderDetail.Date = time.Unix(resp[x].OpenOrders[y].ReceiveTime, 0)
|
||||
orderDetail.Type = orderTypeMap[resp[x].OpenOrders[y].OrderType]
|
||||
if orderDetail.Type == "" {
|
||||
orderDetail.Type = order.UnknownType
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
return orders, nil
|
||||
}
|
||||
@@ -390,19 +390,19 @@ func (a *Alphapoint) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
RemainingAmount: resp[x].OpenOrders[y].QtyRemaining,
|
||||
}
|
||||
|
||||
orderDetail.OrderSide = orderSideMap[resp[x].OpenOrders[y].Side]
|
||||
orderDetail.OrderDate = time.Unix(resp[x].OpenOrders[y].ReceiveTime, 0)
|
||||
orderDetail.OrderType = orderTypeMap[resp[x].OpenOrders[y].OrderType]
|
||||
if orderDetail.OrderType == "" {
|
||||
orderDetail.OrderType = order.Unknown
|
||||
orderDetail.Side = orderSideMap[resp[x].OpenOrders[y].Side]
|
||||
orderDetail.Date = time.Unix(resp[x].OpenOrders[y].ReceiveTime, 0)
|
||||
orderDetail.Type = orderTypeMap[resp[x].OpenOrders[y].OrderType]
|
||||
if orderDetail.Type == "" {
|
||||
orderDetail.Type = order.UnknownType
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -26,17 +26,18 @@ const (
|
||||
apiURL = "https://api.binance.com"
|
||||
|
||||
// Public endpoints
|
||||
exchangeInfo = "/api/v1/exchangeInfo"
|
||||
orderBookDepth = "/api/v1/depth"
|
||||
recentTrades = "/api/v1/trades"
|
||||
historicalTrades = "/api/v1/historicalTrades"
|
||||
aggregatedTrades = "/api/v1/aggTrades"
|
||||
candleStick = "/api/v1/klines"
|
||||
averagePrice = "/api/v3/avgPrice"
|
||||
priceChange = "/api/v1/ticker/24hr"
|
||||
symbolPrice = "/api/v3/ticker/price"
|
||||
bestPrice = "/api/v3/ticker/bookTicker"
|
||||
accountInfo = "/api/v3/account"
|
||||
exchangeInfo = "/api/v1/exchangeInfo"
|
||||
orderBookDepth = "/api/v1/depth"
|
||||
recentTrades = "/api/v1/trades"
|
||||
historicalTrades = "/api/v1/historicalTrades"
|
||||
aggregatedTrades = "/api/v1/aggTrades"
|
||||
candleStick = "/api/v1/klines"
|
||||
averagePrice = "/api/v3/avgPrice"
|
||||
priceChange = "/api/v1/ticker/24hr"
|
||||
symbolPrice = "/api/v3/ticker/price"
|
||||
bestPrice = "/api/v3/ticker/bookTicker"
|
||||
accountInfo = "/api/v3/account"
|
||||
userAccountStream = "/api/v3/userDataStream"
|
||||
|
||||
// Authenticated endpoints
|
||||
newOrderTest = "/api/v3/order/test"
|
||||
@@ -683,3 +684,51 @@ func (b *Binance) GetDepositAddressForCurrency(currency string) (string, error)
|
||||
return resp.Address,
|
||||
b.SendAuthHTTPRequest(http.MethodGet, path, params, request.Unset, &resp)
|
||||
}
|
||||
|
||||
// GetWsAuthStreamKey will retrieve a key to use for authorised WS streaming
|
||||
func (b *Binance) GetWsAuthStreamKey() (string, error) {
|
||||
var resp UserAccountStream
|
||||
path := b.API.Endpoints.URL + userAccountStream
|
||||
headers := make(map[string]string)
|
||||
headers["X-MBX-APIKEY"] = b.API.Credentials.Key
|
||||
err := b.SendPayload(&request.Item{
|
||||
Method: http.MethodPost,
|
||||
Path: path,
|
||||
Headers: headers,
|
||||
Body: bytes.NewBuffer(nil),
|
||||
Result: &resp,
|
||||
AuthRequest: true,
|
||||
Verbose: b.Verbose,
|
||||
HTTPDebugging: b.HTTPDebugging,
|
||||
HTTPRecording: b.HTTPRecording,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.ListenKey, nil
|
||||
}
|
||||
|
||||
// MaintainWsAuthStreamKey will keep the key alive
|
||||
func (b *Binance) MaintainWsAuthStreamKey() error {
|
||||
var err error
|
||||
if listenKey == "" {
|
||||
listenKey, err = b.GetWsAuthStreamKey()
|
||||
return err
|
||||
}
|
||||
path := b.API.Endpoints.URL + userAccountStream
|
||||
params := url.Values{}
|
||||
params.Set("listenKey", listenKey)
|
||||
path = common.EncodeURLValues(path, params)
|
||||
headers := make(map[string]string)
|
||||
headers["X-MBX-APIKEY"] = b.API.Credentials.Key
|
||||
return b.SendPayload(&request.Item{
|
||||
Method: http.MethodPut,
|
||||
Path: path,
|
||||
Headers: headers,
|
||||
Body: bytes.NewBuffer(nil),
|
||||
AuthRequest: true,
|
||||
Verbose: b.Verbose,
|
||||
HTTPDebugging: b.HTTPDebugging,
|
||||
HTTPRecording: b.HTTPRecording,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("Binance setup error", err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
log.Printf(sharedtestvalues.LiveTesting, b.Name, b.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
b.HTTPClient = newClient
|
||||
b.API.Endpoints.URL = serverDetails
|
||||
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -302,14 +302,14 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
if err == nil {
|
||||
t.Error("Expected: 'At least one currency is required to fetch order history'. received nil")
|
||||
}
|
||||
|
||||
getOrdersRequest.Currencies = []currency.Pair{
|
||||
getOrdersRequest.Pairs = []currency.Pair{
|
||||
currency.NewPair(currency.LTC, currency.BTC),
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := b.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -336,7 +336,7 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
t.Error("Expected: 'At least one currency is required to fetch order history'. received nil")
|
||||
}
|
||||
|
||||
getOrdersRequest.Currencies = []currency.Pair{
|
||||
getOrdersRequest.Pairs = []currency.Pair{
|
||||
currency.NewPair(currency.LTC,
|
||||
currency.BTC)}
|
||||
|
||||
@@ -367,11 +367,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.LTC,
|
||||
Quote: currency.BTC,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1000000000,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1000000000,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
|
||||
_, err := b.SubmitOrder(orderSubmission)
|
||||
@@ -392,10 +392,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
}
|
||||
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
@@ -416,10 +416,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
}
|
||||
|
||||
_, err := b.CancelAllOrders(orderCancellation)
|
||||
@@ -514,3 +514,279 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
t.Error("Mock GetDepositAddress() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSSubscriptionHandling(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "SUBSCRIBE",
|
||||
"params": [
|
||||
"btcusdt@aggTrade",
|
||||
"btcusdt@depth"
|
||||
],
|
||||
"id": 1
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSUnsubscriptionHandling(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "UNSUBSCRIBE",
|
||||
"params": [
|
||||
"btcusdt@depth"
|
||||
],
|
||||
"id": 312
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderUpdateHandling(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{
|
||||
"e": "executionReport",
|
||||
"E": 1499405658658,
|
||||
"s": "BTCUSDT",
|
||||
"c": "mUvoqJxFIILMdfAW5iGSOW",
|
||||
"S": "BUY",
|
||||
"o": "LIMIT",
|
||||
"f": "GTC",
|
||||
"q": "1.00000000",
|
||||
"p": "0.10264410",
|
||||
"P": "0.00000000",
|
||||
"F": "0.00000000",
|
||||
"g": -1,
|
||||
"C": null,
|
||||
"x": "NEW",
|
||||
"X": "NEW",
|
||||
"r": "NONE",
|
||||
"i": 4293153,
|
||||
"l": "0.00000000",
|
||||
"z": "0.00000000",
|
||||
"L": "0.00000000",
|
||||
"n": "0",
|
||||
"N": null,
|
||||
"T": 1499405658657,
|
||||
"t": -1,
|
||||
"I": 8641984,
|
||||
"w": true,
|
||||
"m": false,
|
||||
"M": false,
|
||||
"O": 1499405658657,
|
||||
"Z": "0.00000000",
|
||||
"Y": "0.00000000",
|
||||
"Q": "0.00000000"
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOutboundAccountPosition(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{
|
||||
"e": "outboundAccountPosition",
|
||||
"E": 1564034571105,
|
||||
"u": 1564034571073,
|
||||
"B": [
|
||||
{
|
||||
"a": "ETH",
|
||||
"f": "10000.000000",
|
||||
"l": "0.000000"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTickerUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{"stream":"btcusdt@ticker","data":{"e":"24hrTicker","E":1580254809477,"s":"BTCUSDT","p":"420.97000000","P":"4.720","w":"9058.27981278","x":"8917.98000000","c":"9338.96000000","Q":"0.17246300","b":"9338.03000000","B":"0.18234600","a":"9339.70000000","A":"0.14097600","o":"8917.99000000","h":"9373.19000000","l":"8862.40000000","v":"72229.53692000","q":"654275356.16896672","O":1580168409456,"C":1580254809456,"F":235294268,"L":235894703,"n":600436}}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsKlineUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{"stream":"btcusdt@kline_1m","data":{
|
||||
"e": "kline",
|
||||
"E": 123456789,
|
||||
"s": "BNBBTC",
|
||||
"k": {
|
||||
"t": 123400000,
|
||||
"T": 123460000,
|
||||
"s": "BNBBTC",
|
||||
"i": "1m",
|
||||
"f": 100,
|
||||
"L": 200,
|
||||
"o": "0.0010",
|
||||
"c": "0.0020",
|
||||
"h": "0.0025",
|
||||
"l": "0.0015",
|
||||
"v": "1000",
|
||||
"n": 100,
|
||||
"x": false,
|
||||
"q": "1.0000",
|
||||
"V": "500",
|
||||
"Q": "0.500",
|
||||
"B": "123456"
|
||||
}
|
||||
}}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTradeUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{"stream":"btcusdt@trade","data":{
|
||||
"e": "trade",
|
||||
"E": 123456789,
|
||||
"s": "BNBBTC",
|
||||
"t": 12345,
|
||||
"p": "0.001",
|
||||
"q": "100",
|
||||
"b": 88,
|
||||
"a": 50,
|
||||
"T": 123456785,
|
||||
"m": true,
|
||||
"M": true
|
||||
}}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsDepthUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{"stream":"btcusdt@depth","data":{
|
||||
"e": "depthUpdate",
|
||||
"E": 123456789,
|
||||
"s": "BTCUSDT",
|
||||
"U": 157,
|
||||
"u": 160,
|
||||
"b": [
|
||||
[
|
||||
"0.0024",
|
||||
"10"
|
||||
]
|
||||
],
|
||||
"a": [
|
||||
[
|
||||
"0.0026",
|
||||
"100"
|
||||
]
|
||||
]
|
||||
}}`)
|
||||
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err.Error() != "Binance - UpdateLocalCache error: ob.Base could not be found for Exchange Binance CurrencyPair: BTC-USDT AssetType: spot" {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsBalanceUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{
|
||||
"e": "balanceUpdate",
|
||||
"E": 1573200697110,
|
||||
"a": "BTC",
|
||||
"d": "100.00000000",
|
||||
"T": 1573200697068
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOCO(t *testing.T) {
|
||||
t.Parallel()
|
||||
pressXToJSON := []byte(`{
|
||||
"e": "listStatus",
|
||||
"E": 1564035303637,
|
||||
"s": "ETHBTC",
|
||||
"g": 2,
|
||||
"c": "OCO",
|
||||
"l": "EXEC_STARTED",
|
||||
"L": "EXECUTING",
|
||||
"r": "NONE",
|
||||
"C": "F4QN4G8DlFATFlIUQ0cjdD",
|
||||
"T": 1564035303625,
|
||||
"O": [
|
||||
{
|
||||
"s": "ETHBTC",
|
||||
"i": 17,
|
||||
"c": "AJYsMjErWJesZvqlJCTUgL"
|
||||
},
|
||||
{
|
||||
"s": "ETHBTC",
|
||||
"i": 18,
|
||||
"c": "bfYPSQdLoqAJeNrOr9adzq"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWsAuthStreamKey(t *testing.T) {
|
||||
key, err := b.GetWsAuthStreamKey()
|
||||
switch {
|
||||
case mockTests && err != nil,
|
||||
!mockTests && areTestAPIKeysSet() && err != nil:
|
||||
t.Fatal(err)
|
||||
case !mockTests && !areTestAPIKeysSet() && err == nil:
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
|
||||
if key == "" {
|
||||
t.Error("Expected key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaintainWsAuthStreamKey(t *testing.T) {
|
||||
err := b.MaintainWsAuthStreamKey()
|
||||
switch {
|
||||
case mockTests && err != nil,
|
||||
!mockTests && areTestAPIKeysSet() && err != nil:
|
||||
t.Fatal(err)
|
||||
case !mockTests && !areTestAPIKeysSet() && err == nil:
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionTypeToOrderStatus(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Status
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "NEW", Result: order.New},
|
||||
{Case: "CANCELLED", Result: order.Cancelled},
|
||||
{Case: "REJECTED", Result: order.Rejected},
|
||||
{Case: "TRADE", Result: order.PartiallyFilled},
|
||||
{Case: "EXPIRED", Result: order.Expired},
|
||||
{Case: "LOL", Result: order.UnknownStatus},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderStatus(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package binance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
@@ -119,12 +117,6 @@ type RecentTrade struct {
|
||||
IsBestMatch bool `json:"isBestMatch"`
|
||||
}
|
||||
|
||||
// MultiStreamData holds stream data
|
||||
type MultiStreamData struct {
|
||||
Stream string `json:"stream"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// TradeStream holds the trade stream data
|
||||
type TradeStream struct {
|
||||
EventType string `json:"e"`
|
||||
@@ -146,22 +138,22 @@ type KlineStream struct {
|
||||
EventTime int64 `json:"E"`
|
||||
Symbol string `json:"s"`
|
||||
Kline struct {
|
||||
StartTime int64 `json:"t"`
|
||||
CloseTime int64 `json:"T"`
|
||||
Symbol string `json:"s"`
|
||||
Interval string `json:"i"`
|
||||
FirstTradeID int64 `json:"f"`
|
||||
LastTradeID int64 `json:"L"`
|
||||
OpenPrice string `json:"o"`
|
||||
ClosePrice string `json:"c"`
|
||||
HighPrice string `json:"h"`
|
||||
LowPrice string `json:"l"`
|
||||
Volume string `json:"v"`
|
||||
NumberOfTrades int64 `json:"n"`
|
||||
KlineClosed bool `json:"x"`
|
||||
Quote string `json:"q"`
|
||||
TakerBuyBaseAssetVolume string `json:"V"`
|
||||
TakerBuyQuoteAssetVolume string `json:"Q"`
|
||||
StartTime int64 `json:"t"`
|
||||
CloseTime int64 `json:"T"`
|
||||
Symbol string `json:"s"`
|
||||
Interval string `json:"i"`
|
||||
FirstTradeID int64 `json:"f"`
|
||||
LastTradeID int64 `json:"L"`
|
||||
OpenPrice float64 `json:"o,string"`
|
||||
ClosePrice float64 `json:"c,string"`
|
||||
HighPrice float64 `json:"h,string"`
|
||||
LowPrice float64 `json:"l,string"`
|
||||
Volume float64 `json:"v,string"`
|
||||
NumberOfTrades int64 `json:"n"`
|
||||
KlineClosed bool `json:"x"`
|
||||
Quote float64 `json:"q,string"`
|
||||
TakerBuyBaseAssetVolume float64 `json:"V,string"`
|
||||
TakerBuyQuoteAssetVolume float64 `json:"Q,string"`
|
||||
} `json:"k"`
|
||||
}
|
||||
|
||||
@@ -612,3 +604,97 @@ type WithdrawResponse struct {
|
||||
Msg string `json:"msg"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// UserAccountStream contains a key to maintain an authorised
|
||||
// websocket connection
|
||||
type UserAccountStream struct {
|
||||
ListenKey string `json:"listenKey"`
|
||||
}
|
||||
|
||||
type wsAccountInfo struct {
|
||||
CanDeposit bool `json:"D"`
|
||||
CanTrade bool `json:"T"`
|
||||
CanWithdraw bool `json:"W"`
|
||||
EventTime int64 `json:"E"`
|
||||
LastUpdated int64 `json:"u"`
|
||||
BuyerCommission float64 `json:"b"`
|
||||
MakerCommission float64 `json:"m"`
|
||||
SellerCommission float64 `json:"s"`
|
||||
TakerCommission float64 `json:"t"`
|
||||
EventType string `json:"e"`
|
||||
Currencies []struct {
|
||||
Asset string `json:"a"`
|
||||
Available float64 `json:"f,string"`
|
||||
Locked float64 `json:"l,string"`
|
||||
} `json:"B"`
|
||||
}
|
||||
|
||||
type wsAccountPosition struct {
|
||||
Currencies []struct {
|
||||
Asset string `json:"a"`
|
||||
Available float64 `json:"f,string"`
|
||||
Locked float64 `json:"l,string"`
|
||||
} `json:"B"`
|
||||
EventTime int64 `json:"E"`
|
||||
LastUpdated int64 `json:"u"`
|
||||
EventType string `json:"e"`
|
||||
}
|
||||
|
||||
type wsBalanceUpdate struct {
|
||||
EventTime int64 `json:"E"`
|
||||
ClearTime int64 `json:"T"`
|
||||
BalanceDelta float64 `json:"d,string"`
|
||||
Asset string `json:"a"`
|
||||
EventType string `json:"e"`
|
||||
}
|
||||
|
||||
type wsOrderUpdate struct {
|
||||
ClientOrderID string `json:"C"`
|
||||
EventTime int64 `json:"E"`
|
||||
IcebergQuantity float64 `json:"F,string"`
|
||||
LastExecutedPrice float64 `json:"L,string"`
|
||||
CommissionAsset float64 `json:"N"`
|
||||
OrderCreationTime int64 `json:"O"`
|
||||
StopPrice float64 `json:"P,string"`
|
||||
QuoteOrderQuantity float64 `json:"Q,string"`
|
||||
Side string `json:"S"`
|
||||
TransactionTime int64 `json:"T"`
|
||||
OrderStatus string `json:"X"`
|
||||
LastQuoteAssetTransactedQuantity float64 `json:"Y,string"`
|
||||
CumulativeQuoteTransactedQuantity float64 `json:"Z,string"`
|
||||
CancelledClientOrderID string `json:"c"`
|
||||
EventType string `json:"e"`
|
||||
TimeInForce string `json:"f"`
|
||||
OrderListID int64 `json:"g"`
|
||||
OrderID int64 `json:"i"`
|
||||
LastExecutedQuantity float64 `json:"l,string"`
|
||||
IsMaker bool `json:"m"`
|
||||
Commission float64 `json:"n,string"`
|
||||
OrderType string `json:"o"`
|
||||
Price float64 `json:"p,string"`
|
||||
Quantity float64 `json:"q,string"`
|
||||
RejectionReason string `json:"r"`
|
||||
Symbol string `json:"s"`
|
||||
TradeID int64 `json:"t"`
|
||||
IsOnOrderBook bool `json:"w"`
|
||||
CurrentExecutionType string `json:"x"`
|
||||
CumulativeFilledQuantity float64 `json:"z,string"`
|
||||
}
|
||||
|
||||
type wsListStauts struct {
|
||||
ListClientOrderID string `json:"C"`
|
||||
EventTime int64 `json:"E"`
|
||||
ListOrderStatus string `json:"L"`
|
||||
Orders []struct {
|
||||
ClientOrderID string `json:"c"`
|
||||
OrderID int64 `json:"i"`
|
||||
Symbol string `json:"s"`
|
||||
} `json:"O"`
|
||||
TransactionTime int64 `json:"T"`
|
||||
ContingencyType string `json:"c"`
|
||||
EventType string `json:"e"`
|
||||
OrderListID int64 `json:"g"`
|
||||
ListStatusType string `json:"l"`
|
||||
RejectionReason string `json:"r"`
|
||||
Symbol string `json:"s"`
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,6 +25,8 @@ const (
|
||||
pingDelay = time.Minute * 9
|
||||
)
|
||||
|
||||
var listenKey string
|
||||
|
||||
// WsConnect intiates a websocket connection
|
||||
func (b *Binance) WsConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
@@ -31,6 +35,13 @@ func (b *Binance) WsConnect() error {
|
||||
|
||||
var dialer websocket.Dialer
|
||||
var err error
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
listenKey, err = b.GetWsAuthStreamKey()
|
||||
if err != nil {
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
log.Errorf(log.ExchangeSys, "%v unable to connect to authenticated Websocket. Error: %s", b.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
pairs := b.GetEnabledPairs(asset.Spot).Strings()
|
||||
tick := strings.ToLower(
|
||||
@@ -55,6 +66,11 @@ func (b *Binance) WsConnect() error {
|
||||
kline +
|
||||
"/" +
|
||||
depth
|
||||
if listenKey != "" {
|
||||
wsurl += "/" +
|
||||
listenKey
|
||||
}
|
||||
|
||||
enabledPairs := b.GetEnabledPairs(asset.Spot)
|
||||
for i := range enabledPairs {
|
||||
err = b.SeedLocalCache(enabledPairs[i])
|
||||
@@ -77,13 +93,36 @@ func (b *Binance) WsConnect() error {
|
||||
MessageType: websocket.PongMessage,
|
||||
Delay: pingDelay,
|
||||
})
|
||||
go b.WsHandleData()
|
||||
|
||||
go b.wsReadData()
|
||||
go b.KeepAuthKeyAlive()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles websocket data from WsReadData
|
||||
func (b *Binance) WsHandleData() {
|
||||
// KeepAuthKeyAlive will continuously send messages to
|
||||
// keep the WS auth key active
|
||||
func (b *Binance) KeepAuthKeyAlive() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
ticks := time.NewTicker(time.Minute * 30)
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
ticks.Stop()
|
||||
return
|
||||
case <-ticks.C:
|
||||
err := b.MaintainWsAuthStreamKey()
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
log.Warnf(log.ExchangeSys, b.Name+" - Unable to renew auth websocket token, may experience shutdown")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *Binance) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
@@ -94,144 +133,273 @@ func (b *Binance) WsHandleData() {
|
||||
return
|
||||
|
||||
default:
|
||||
read, err := b.WebsocketConn.ReadMessage()
|
||||
resp, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
var multiStreamData MultiStreamData
|
||||
err = json.Unmarshal(read.Raw, &multiStreamData)
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not load multi stream data: %s",
|
||||
b.Name,
|
||||
read.Raw)
|
||||
continue
|
||||
}
|
||||
streamType := strings.Split(multiStreamData.Stream, "@")
|
||||
switch streamType[1] {
|
||||
case "trade":
|
||||
trade := TradeStream{}
|
||||
err := json.Unmarshal(multiStreamData.Data, &trade)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not unmarshal trade data: %s",
|
||||
b.Name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
price, err := strconv.ParseFloat(trade.Price, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - price conversion error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(trade.Quantity, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - amount conversion error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
CurrencyPair: currency.NewPairFromFormattedPairs(trade.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)),
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
Side: trade.EventType,
|
||||
}
|
||||
continue
|
||||
case "ticker":
|
||||
t := TickerStream{}
|
||||
err := json.Unmarshal(multiStreamData.Data, &t)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to a TickerStream structure %s",
|
||||
b.Name,
|
||||
err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: b.Name,
|
||||
Open: t.OpenPrice,
|
||||
Close: t.ClosePrice,
|
||||
Volume: t.TotalTradedVolume,
|
||||
QuoteVolume: t.TotalTradedQuoteVolume,
|
||||
High: t.HighPrice,
|
||||
Low: t.LowPrice,
|
||||
Bid: t.BestBidPrice,
|
||||
Ask: t.BestAskPrice,
|
||||
Last: t.LastPrice,
|
||||
LastUpdated: time.Unix(0, t.EventTime*int64(time.Millisecond)),
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromFormattedPairs(t.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
}
|
||||
|
||||
continue
|
||||
case "kline_1m":
|
||||
kline := KlineStream{}
|
||||
err := json.Unmarshal(multiStreamData.Data, &kline)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to a KlineStream structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
var wsKline wshandler.KlineData
|
||||
wsKline.Timestamp = time.Unix(0, kline.EventTime*int64(time.Millisecond))
|
||||
wsKline.Pair = currency.NewPairFromFormattedPairs(kline.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true))
|
||||
wsKline.AssetType = asset.Spot
|
||||
wsKline.Exchange = b.Name
|
||||
wsKline.StartTime = time.Unix(0, kline.Kline.StartTime*int64(time.Millisecond))
|
||||
wsKline.CloseTime = time.Unix(0, kline.Kline.CloseTime*int64(time.Millisecond))
|
||||
wsKline.Interval = kline.Kline.Interval
|
||||
wsKline.OpenPrice, _ = strconv.ParseFloat(kline.Kline.OpenPrice, 64)
|
||||
wsKline.ClosePrice, _ = strconv.ParseFloat(kline.Kline.ClosePrice, 64)
|
||||
wsKline.HighPrice, _ = strconv.ParseFloat(kline.Kline.HighPrice, 64)
|
||||
wsKline.LowPrice, _ = strconv.ParseFloat(kline.Kline.LowPrice, 64)
|
||||
wsKline.Volume, _ = strconv.ParseFloat(kline.Kline.Volume, 64)
|
||||
b.Websocket.DataHandler <- wsKline
|
||||
continue
|
||||
case "depth":
|
||||
depth := WebsocketDepthStream{}
|
||||
err := json.Unmarshal(multiStreamData.Data, &depth)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - Could not convert to depthStream structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.UpdateLocalCache(&depth)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v - UpdateLocalCache error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
currencyPair := currency.NewPairFromFormattedPairs(depth.Pair, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true))
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
continue
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
var multiStreamData map[string]interface{}
|
||||
err := json.Unmarshal(respRaw, &multiStreamData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if method, ok := multiStreamData["method"].(string); ok {
|
||||
// TODO handle subscription handling
|
||||
if strings.EqualFold(method, "subscribe") {
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(method, "unsubscribe") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if e, ok := multiStreamData["e"].(string); ok {
|
||||
switch e {
|
||||
case "outboundAccountInfo":
|
||||
var data wsAccountInfo
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to outboundAccountInfo structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
case "outboundAccountPosition":
|
||||
var data wsAccountPosition
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to outboundAccountPosition structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
case "balanceUpdate":
|
||||
var data wsBalanceUpdate
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to balanceUpdate structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
case "executionReport":
|
||||
var data wsOrderUpdate
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to executionReport structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
var orderID = strconv.FormatInt(data.OrderID, 10)
|
||||
oType, err := order.StringToOrderType(data.OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(data.Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = stringToOrderStatus(data.CurrentExecutionType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = b.GetRequestFormattedPairAndAssetType(data.Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: data.Price,
|
||||
Amount: data.Quantity,
|
||||
ExecutedAmount: data.CumulativeFilledQuantity,
|
||||
RemainingAmount: data.Quantity - data.CumulativeFilledQuantity,
|
||||
Exchange: b.Name,
|
||||
ID: orderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: time.Unix(0, data.OrderCreationTime*int64(time.Millisecond)),
|
||||
Pair: p,
|
||||
}
|
||||
case "listStatus":
|
||||
var data wsListStauts
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to listStatus structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if stream, ok := multiStreamData["stream"].(string); ok {
|
||||
streamType := strings.Split(stream, "@")
|
||||
if len(streamType) > 1 {
|
||||
if data, ok := multiStreamData["data"]; ok {
|
||||
rawData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch streamType[1] {
|
||||
case "trade":
|
||||
var trade TradeStream
|
||||
err := json.Unmarshal(rawData, &trade)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not unmarshal trade data: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
price, err := strconv.ParseFloat(trade.Price, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - price conversion error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(trade.Quantity, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - amount conversion error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
CurrencyPair: currency.NewPairFromFormattedPairs(trade.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)),
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
case "ticker":
|
||||
var t TickerStream
|
||||
err := json.Unmarshal(rawData, &t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to a TickerStream structure %s",
|
||||
b.Name,
|
||||
err.Error())
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: b.Name,
|
||||
Open: t.OpenPrice,
|
||||
Close: t.ClosePrice,
|
||||
Volume: t.TotalTradedVolume,
|
||||
QuoteVolume: t.TotalTradedQuoteVolume,
|
||||
High: t.HighPrice,
|
||||
Low: t.LowPrice,
|
||||
Bid: t.BestBidPrice,
|
||||
Ask: t.BestAskPrice,
|
||||
Last: t.LastPrice,
|
||||
LastUpdated: time.Unix(0, t.EventTime*int64(time.Millisecond)),
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromFormattedPairs(t.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
}
|
||||
case "kline_1m", "kline_3m", "kline_5m", "kline_15m", "kline_30m", "kline_1h", "kline_2h", "kline_4h",
|
||||
"kline_6h", "kline_8h", "kline_12h", "kline_1d", "kline_3d", "kline_1w", "kline_1M":
|
||||
var kline KlineStream
|
||||
err := json.Unmarshal(rawData, &kline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to a KlineStream structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, kline.EventTime*int64(time.Millisecond)),
|
||||
Pair: currency.NewPairFromFormattedPairs(kline.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
StartTime: time.Unix(0, kline.Kline.StartTime*int64(time.Millisecond)),
|
||||
CloseTime: time.Unix(0, kline.Kline.CloseTime*int64(time.Millisecond)),
|
||||
Interval: kline.Kline.Interval,
|
||||
OpenPrice: kline.Kline.OpenPrice,
|
||||
ClosePrice: kline.Kline.ClosePrice,
|
||||
HighPrice: kline.Kline.HighPrice,
|
||||
LowPrice: kline.Kline.LowPrice,
|
||||
Volume: kline.Kline.Volume,
|
||||
}
|
||||
case "depth":
|
||||
var depth WebsocketDepthStream
|
||||
err := json.Unmarshal(rawData, &depth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to depthStream structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
err = b.UpdateLocalCache(&depth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - UpdateLocalCache error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
currencyPair := currency.NewPairFromFormattedPairs(depth.Pair, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true))
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringToOrderStatus(status string) (order.Status, error) {
|
||||
switch status {
|
||||
case "NEW":
|
||||
return order.New, nil
|
||||
case "CANCELLED":
|
||||
return order.Cancelled, nil
|
||||
case "REJECTED":
|
||||
return order.Rejected, nil
|
||||
case "TRADE":
|
||||
return order.PartiallyFilled, nil
|
||||
case "EXPIRED":
|
||||
return order.Expired, nil
|
||||
default:
|
||||
return order.UnknownStatus, errors.New(status + " not recognised as order status")
|
||||
}
|
||||
}
|
||||
|
||||
// SeedLocalCache seeds depth data
|
||||
func (b *Binance) SeedLocalCache(p currency.Pair) error {
|
||||
var newOrderBook orderbook.Base
|
||||
@@ -294,7 +462,6 @@ func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error {
|
||||
}
|
||||
currencyPair := currency.NewPairFromFormattedPairs(wsdp.Pair, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true))
|
||||
|
||||
return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: updateBid,
|
||||
Asks: updateAsk,
|
||||
|
||||
@@ -96,10 +96,16 @@ func (b *Binance) SetDefaults() {
|
||||
CryptoWithdrawalFee: true,
|
||||
},
|
||||
WebsocketCapabilities: protocol.Features{
|
||||
TradeFetching: true,
|
||||
TickerFetching: true,
|
||||
KlineFetching: true,
|
||||
OrderbookFetching: true,
|
||||
TradeFetching: true,
|
||||
TickerFetching: true,
|
||||
KlineFetching: true,
|
||||
OrderbookFetching: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
AccountInfo: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
@@ -415,14 +421,14 @@ func (b *Binance) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
var sideType string
|
||||
if s.OrderSide == order.Buy {
|
||||
if s.Side == order.Buy {
|
||||
sideType = order.Buy.String()
|
||||
} else {
|
||||
sideType = order.Sell.String()
|
||||
}
|
||||
|
||||
var requestParamsOrderType RequestParamsOrderType
|
||||
switch s.OrderType {
|
||||
switch s.Type {
|
||||
case order.Market:
|
||||
requestParamsOrderType = BinanceRequestParamsOrderMarket
|
||||
case order.Limit:
|
||||
@@ -464,12 +470,12 @@ func (b *Binance) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *Binance) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = b.CancelExistingOrder(b.FormatExchangeCurrency(order.CurrencyPair,
|
||||
_, err = b.CancelExistingOrder(b.FormatExchangeCurrency(order.Pair,
|
||||
order.AssetType).String(),
|
||||
orderIDInt,
|
||||
order.AccountID)
|
||||
@@ -553,13 +559,13 @@ func (b *Binance) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error)
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("at least one currency is required to fetch order history")
|
||||
}
|
||||
|
||||
var orders []order.Detail
|
||||
for x := range req.Currencies {
|
||||
resp, err := b.OpenOrders(b.FormatExchangeCurrency(req.Currencies[x],
|
||||
for x := range req.Pairs {
|
||||
resp, err := b.OpenOrders(b.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -571,21 +577,21 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
orderDate := time.Unix(0, int64(resp[i].Time)*int64(time.Millisecond))
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].OrigQty,
|
||||
OrderDate: orderDate,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
OrderSide: orderSide,
|
||||
OrderType: orderType,
|
||||
Price: resp[i].Price,
|
||||
Status: order.Status(resp[i].Status),
|
||||
CurrencyPair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Amount: resp[i].OrigQty,
|
||||
Date: orderDate,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Price: resp[i].Price,
|
||||
Status: order.Status(resp[i].Status),
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
return orders, nil
|
||||
}
|
||||
@@ -593,13 +599,13 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("at least one currency is required to fetch order history")
|
||||
}
|
||||
|
||||
var orders []order.Detail
|
||||
for x := range req.Currencies {
|
||||
resp, err := b.AllOrders(b.FormatExchangeCurrency(req.Currencies[x],
|
||||
for x := range req.Pairs {
|
||||
resp, err := b.AllOrders(b.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String(),
|
||||
"",
|
||||
"1000")
|
||||
@@ -617,21 +623,21 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].OrigQty,
|
||||
OrderDate: orderDate,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
OrderSide: orderSide,
|
||||
OrderType: orderType,
|
||||
Price: resp[i].Price,
|
||||
CurrencyPair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Status: order.Status(resp[i].Status),
|
||||
Amount: resp[i].OrigQty,
|
||||
Date: orderDate,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Price: resp[i].Price,
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Status: order.Status(resp[i].Status),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -438,7 +438,7 @@ type WebsocketOrder struct {
|
||||
Status string
|
||||
Price float64
|
||||
PriceAvg float64
|
||||
Timestamp string
|
||||
Timestamp int64
|
||||
Notify int
|
||||
}
|
||||
|
||||
@@ -507,13 +507,21 @@ const (
|
||||
wsBalanceUpdate = "bu"
|
||||
wsMarginInfoUpdate = "miu"
|
||||
wsNotification = "n"
|
||||
wsOrderSnapshot = "os"
|
||||
wsOrderNew = "on"
|
||||
wsOrderUpdate = "ou"
|
||||
wsOrderCancel = "oc"
|
||||
wsRequest = "-req"
|
||||
wsOrderNewRequest = wsOrderNew + wsRequest
|
||||
wsOrderUpdateRequest = wsOrderUpdate + wsRequest
|
||||
wsOrderCancelRequest = wsOrderCancel + wsRequest
|
||||
wsFundingOrderSnapshot = "fos"
|
||||
wsFundingOrderNew = "fon"
|
||||
wsFundingOrderUpdate = "fou"
|
||||
wsFundingOrderCancel = "foc"
|
||||
wsFundingOrderNewRequest = wsFundingOrderNew + wsRequest
|
||||
wsFundingOrderUpdateRequest = wsFundingOrderUpdate + wsRequest
|
||||
wsFundingOrderCancelRequest = wsFundingOrderCancel + wsRequest
|
||||
wsCancelMultipleOrders = "oc_multi"
|
||||
wsBook = "book"
|
||||
wsCandles = "candles"
|
||||
@@ -534,22 +542,22 @@ type WsAuthRequest struct {
|
||||
|
||||
// WsFundingOffer funding offer received via websocket
|
||||
type WsFundingOffer struct {
|
||||
ID int64
|
||||
Symbol string
|
||||
Created int64
|
||||
Updated int64
|
||||
Amount float64
|
||||
AmountOrig float64
|
||||
Type string
|
||||
Flags interface{}
|
||||
Status string
|
||||
Rate float64
|
||||
Period int64
|
||||
Notify bool
|
||||
Hidden bool
|
||||
Insure bool
|
||||
Renew bool
|
||||
RateReal float64
|
||||
ID int64
|
||||
Symbol string
|
||||
Created int64
|
||||
Updated int64
|
||||
Amount float64
|
||||
OriginalAmount float64
|
||||
Type string
|
||||
Flags interface{}
|
||||
Status string
|
||||
Rate float64
|
||||
Period int64
|
||||
Notify bool
|
||||
Hidden bool
|
||||
Insure bool
|
||||
Renew bool
|
||||
RateReal float64
|
||||
}
|
||||
|
||||
// WsCredit credit details received via websocket
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,8 @@ func (b *Bitfinex) SetDefaults() {
|
||||
AuthenticatedEndpoints: true,
|
||||
MessageCorrelation: true,
|
||||
DeadMansSwitch: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.AutoWithdrawFiatWithAPIPermission,
|
||||
@@ -436,7 +438,7 @@ func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
submitOrderResponse.OrderID, err = b.WsNewOrder(&WsNewOrderRequest{
|
||||
CustomID: b.AuthenticatedWebsocketConn.GenerateMessageID(false),
|
||||
Type: o.OrderType.String(),
|
||||
Type: o.Type.String(),
|
||||
Symbol: b.FormatExchangeCurrency(o.Pair, asset.Spot).String(),
|
||||
Amount: o.Amount,
|
||||
Price: o.Price,
|
||||
@@ -446,10 +448,10 @@ func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
} else {
|
||||
var response Order
|
||||
isBuying := o.OrderSide == order.Buy
|
||||
isBuying := o.Side == order.Buy
|
||||
b.appendOptionalDelimiter(&o.Pair)
|
||||
response, err = b.NewOrder(o.Pair.String(),
|
||||
o.OrderType.String(),
|
||||
o.Type.String(),
|
||||
o.Amount,
|
||||
o.Price,
|
||||
false,
|
||||
@@ -472,9 +474,9 @@ func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (b *Bitfinex) ModifyOrder(action *order.Modify) (string, error) {
|
||||
orderIDInt, err := strconv.ParseInt(action.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(action.ID, 10, 64)
|
||||
if err != nil {
|
||||
return action.OrderID, err
|
||||
return action.ID, err
|
||||
}
|
||||
if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
if action.Side == order.Sell && action.Amount > 0 {
|
||||
@@ -485,14 +487,14 @@ func (b *Bitfinex) ModifyOrder(action *order.Modify) (string, error) {
|
||||
Price: action.Price,
|
||||
Amount: action.Amount,
|
||||
})
|
||||
return action.OrderID, err
|
||||
return action.ID, err
|
||||
}
|
||||
return "", common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *Bitfinex) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -623,13 +625,13 @@ func (b *Bitfinex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
|
||||
orderDetail := order.Detail{
|
||||
Amount: resp[i].OriginalAmount,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
OrderSide: orderSide,
|
||||
Side: orderSide,
|
||||
Price: resp[i].Price,
|
||||
RemainingAmount: resp[i].RemainingAmount,
|
||||
CurrencyPair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
ExecutedAmount: resp[i].ExecutedAmount,
|
||||
}
|
||||
|
||||
@@ -648,18 +650,18 @@ func (b *Bitfinex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
// Return type suggests “market” / “limit” / “stop” / “trailing-stop”
|
||||
orderType := strings.Replace(resp[i].Type, "exchange ", "", 1)
|
||||
if orderType == "trailing-stop" {
|
||||
orderDetail.OrderType = order.TrailingStop
|
||||
orderDetail.Type = order.TrailingStop
|
||||
} else {
|
||||
orderDetail.OrderType = order.Type(strings.ToUpper(orderType))
|
||||
orderDetail.Type = order.Type(strings.ToUpper(orderType))
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -682,14 +684,14 @@ func (b *Bitfinex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
|
||||
orderDetail := order.Detail{
|
||||
Amount: resp[i].OriginalAmount,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
OrderSide: orderSide,
|
||||
Side: orderSide,
|
||||
Price: resp[i].Price,
|
||||
RemainingAmount: resp[i].RemainingAmount,
|
||||
ExecutedAmount: resp[i].ExecutedAmount,
|
||||
CurrencyPair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -707,21 +709,21 @@ func (b *Bitfinex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
// Return type suggests “market” / “limit” / “stop” / “trailing-stop”
|
||||
orderType := strings.Replace(resp[i].Type, "exchange ", "", 1)
|
||||
if orderType == "trailing-stop" {
|
||||
orderDetail.OrderType = order.TrailingStop
|
||||
orderDetail.Type = order.TrailingStop
|
||||
} else {
|
||||
orderDetail.OrderType = order.Type(strings.ToUpper(orderType))
|
||||
orderDetail.Type = order.Type(strings.ToUpper(orderType))
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
for i := range req.Currencies {
|
||||
b.appendOptionalDelimiter(&req.Currencies[i])
|
||||
for i := range req.Pairs {
|
||||
b.appendOptionalDelimiter(&req.Pairs[i])
|
||||
}
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -282,7 +282,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := b.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -308,11 +308,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.LTC,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
_, err := b.SubmitOrder(orderSubmission)
|
||||
if err != common.ErrNotYetImplemented {
|
||||
@@ -328,10 +328,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
@@ -349,10 +349,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
_, err := b.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -308,8 +308,8 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
OrderSide: order.Sell,
|
||||
Type: order.AnyType,
|
||||
Side: order.Sell,
|
||||
}
|
||||
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -323,7 +323,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := b.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -351,11 +351,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.LTC,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := b.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -373,10 +373,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
@@ -396,10 +396,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := b.CancelAllOrders(orderCancellation)
|
||||
@@ -435,11 +435,11 @@ func TestModifyOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
_, err := b.ModifyOrder(&order.Modify{
|
||||
OrderID: "1337",
|
||||
Price: 100,
|
||||
Amount: 1000,
|
||||
Side: order.Sell,
|
||||
CurrencyPair: curr})
|
||||
ID: "1337",
|
||||
Price: 100,
|
||||
Amount: 1000,
|
||||
Side: order.Sell,
|
||||
Pair: curr})
|
||||
if err == nil {
|
||||
t.Error("ModifyOrder() Expected error")
|
||||
}
|
||||
|
||||
@@ -325,14 +325,14 @@ func (b *Bithumb) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
|
||||
var orderID string
|
||||
var err error
|
||||
if s.OrderSide == order.Buy {
|
||||
if s.Side == order.Buy {
|
||||
var result MarketBuy
|
||||
result, err = b.MarketBuyOrder(s.Pair.Base.String(), s.Amount)
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
orderID = result.OrderID
|
||||
} else if s.OrderSide == order.Sell {
|
||||
} else if s.Side == order.Sell {
|
||||
var result MarketSell
|
||||
result, err = b.MarketSellOrder(s.Pair.Base.String(), s.Amount)
|
||||
if err != nil {
|
||||
@@ -352,8 +352,8 @@ func (b *Bithumb) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (b *Bithumb) ModifyOrder(action *order.Modify) (string, error) {
|
||||
order, err := b.ModifyTrade(action.OrderID,
|
||||
action.CurrencyPair.Base.String(),
|
||||
order, err := b.ModifyTrade(action.ID,
|
||||
action.Pair.Base.String(),
|
||||
action.Side.Lower(),
|
||||
action.Amount,
|
||||
int64(action.Price))
|
||||
@@ -368,8 +368,8 @@ func (b *Bithumb) ModifyOrder(action *order.Modify) (string, error) {
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *Bithumb) CancelOrder(order *order.Cancel) error {
|
||||
_, err := b.CancelTrade(order.Side.String(),
|
||||
order.OrderID,
|
||||
order.CurrencyPair.Base.String())
|
||||
order.ID,
|
||||
order.Pair.Base.String())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -396,7 +396,7 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *order.Cancel) (order.Cancel
|
||||
for i := range allOrders {
|
||||
_, err := b.CancelTrade(orderCancellation.Side.String(),
|
||||
allOrders[i].OrderID,
|
||||
orderCancellation.CurrencyPair.Base.String())
|
||||
orderCancellation.Pair.Base.String())
|
||||
if err != nil {
|
||||
cancelAllOrdersResponse.Status[allOrders[i].OrderID] = err.Error()
|
||||
}
|
||||
@@ -498,27 +498,27 @@ func (b *Bithumb) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Amount: resp.Data[i].Units,
|
||||
Exchange: b.Name,
|
||||
ID: resp.Data[i].OrderID,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
Price: resp.Data[i].Price,
|
||||
RemainingAmount: resp.Data[i].UnitsRemaining,
|
||||
Status: order.Active,
|
||||
CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency,
|
||||
Pair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency,
|
||||
resp.Data[i].PaymentCurrency,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
}
|
||||
|
||||
if resp.Data[i].Type == "bid" {
|
||||
orderDetail.OrderSide = order.Buy
|
||||
orderDetail.Side = order.Buy
|
||||
} else if resp.Data[i].Type == "ask" {
|
||||
orderDetail.OrderSide = order.Sell
|
||||
orderDetail.Side = order.Sell
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -541,26 +541,26 @@ func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Amount: resp.Data[i].Units,
|
||||
Exchange: b.Name,
|
||||
ID: resp.Data[i].OrderID,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
Price: resp.Data[i].Price,
|
||||
RemainingAmount: resp.Data[i].UnitsRemaining,
|
||||
CurrencyPair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency,
|
||||
Pair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency,
|
||||
resp.Data[i].PaymentCurrency,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
}
|
||||
|
||||
if resp.Data[i].Type == "bid" {
|
||||
orderDetail.OrderSide = order.Buy
|
||||
orderDetail.Side = order.Buy
|
||||
} else if resp.Data[i].Type == "ask" {
|
||||
orderDetail.OrderSide = order.Sell
|
||||
orderDetail.Side = order.Sell
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -258,38 +258,38 @@ func (p LeaderboardGetParams) IsNil() bool {
|
||||
|
||||
// OrderNewParams contains all the parameters to send to the API endpoint
|
||||
type OrderNewParams struct {
|
||||
// ClOrdID - [Optional] Client Order ID. This clOrdID will come back on the
|
||||
// ClientOrderID - [Optional] Client Order ID. This clOrdID will come back on the
|
||||
// order and any related executions.
|
||||
ClOrdID string `json:"clOrdID,omitempty"`
|
||||
ClientOrderID string `json:"clOrdID,omitempty"`
|
||||
|
||||
// ClOrdLinkID - [Optional] Client Order Link ID for contingent orders.
|
||||
ClOrdLinkID string `json:"clOrdLinkID,omitempty"`
|
||||
// ClientOrderLinkID - [Optional] Client Order Link ID for contingent orders.
|
||||
ClientOrderLinkID string `json:"clOrdLinkID,omitempty"`
|
||||
|
||||
// ContingencyType - [Optional] contingency type for use with `clOrdLinkID`.
|
||||
// Valid options: OneCancelsTheOther, OneTriggersTheOther,
|
||||
// OneUpdatesTheOtherAbsolute, OneUpdatesTheOtherProportional.
|
||||
ContingencyType string `json:"contingencyType,omitempty"`
|
||||
|
||||
// DisplayQty - [Optional] quantity to display in the book. Use 0 for a fully
|
||||
// DisplayQuantity- [Optional] quantity to display in the book. Use 0 for a fully
|
||||
// hidden order.
|
||||
DisplayQty float64 `json:"displayQty,omitempty"`
|
||||
DisplayQuantity float64 `json:"displayQty,omitempty"`
|
||||
|
||||
// ExecInst - [Optional] execution instructions. Valid options:
|
||||
// ExecutionInstance - [Optional] execution instructions. Valid options:
|
||||
// ParticipateDoNotInitiate, AllOrNone, MarkPrice, IndexPrice, LastPrice,
|
||||
// Close, ReduceOnly, Fixed. 'AllOrNone' instruction requires `displayQty`
|
||||
// to be 0. 'MarkPrice', 'IndexPrice' or 'LastPrice' instruction valid for
|
||||
// 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders.
|
||||
ExecInst string `json:"execInst,omitempty"`
|
||||
|
||||
// OrdType - Order type. Valid options: Market, Limit, Stop, StopLimit,
|
||||
// OrderType - Order type. Valid options: Market, Limit, Stop, StopLimit,
|
||||
// MarketIfTouched, LimitIfTouched, MarketWithLeftOverAsLimit, Pegged.
|
||||
// Defaults to 'Limit' when `price` is specified. Defaults to 'Stop' when
|
||||
// `stopPx` is specified. Defaults to 'StopLimit' when `price` and `stopPx`
|
||||
// are specified.
|
||||
OrdType string `json:"ordType,omitempty"`
|
||||
OrderType string `json:"ordType,omitempty"`
|
||||
|
||||
// OrderQty Order quantity in units of the instrument (i.e. contracts).
|
||||
OrderQty float64 `json:"orderQty,omitempty"`
|
||||
// OrderQuantity Order quantity in units of the instrument (i.e. contracts).
|
||||
OrderQuantity float64 `json:"orderQty,omitempty"`
|
||||
|
||||
// PegOffsetValue - [Optional] trailing offset from the current price for
|
||||
// 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders; use a
|
||||
@@ -309,11 +309,11 @@ type OrderNewParams struct {
|
||||
// `orderQty` or `simpleOrderQty` is negative.
|
||||
Side string `json:"side,omitempty"`
|
||||
|
||||
// SimpleOrderQty - Order quantity in units of the underlying instrument
|
||||
// SimpleOrderQuantity - Order quantity in units of the underlying instrument
|
||||
// (i.e. Bitcoin).
|
||||
SimpleOrderQty float64 `json:"simpleOrderQty,omitempty"`
|
||||
SimpleOrderQuantity float64 `json:"simpleOrderQty,omitempty"`
|
||||
|
||||
// StopPx - [Optional] trigger price for 'Stop', 'StopLimit',
|
||||
// StopPrice - [Optional] trigger price for 'Stop', 'StopLimit',
|
||||
// 'MarketIfTouched', and 'LimitIfTouched' orders. Use a price below the
|
||||
// current price for stop-sell orders and buy-if-touched orders. Use
|
||||
// `execInst` of 'MarkPrice' or 'LastPrice' to define the current price used
|
||||
@@ -351,16 +351,16 @@ func (p *OrderNewParams) IsNil() bool {
|
||||
// OrderAmendParams contains all the parameters to send to the API endpoint
|
||||
// for the order amend operation
|
||||
type OrderAmendParams struct {
|
||||
// ClOrdID - [Optional] new Client Order ID, requires `origClOrdID`.
|
||||
ClOrdID string `json:"clOrdID,omitempty"`
|
||||
// ClientOrderID - [Optional] new Client Order ID, requires `origClOrdID`.
|
||||
ClientOrderID string `json:"clOrdID,omitempty"`
|
||||
|
||||
// LeavesQty - [Optional] leaves quantity in units of the instrument
|
||||
// LeavesQuantity - [Optional] leaves quantity in units of the instrument
|
||||
// (i.e. contracts). Useful for amending partially filled orders.
|
||||
LeavesQty int32 `json:"leavesQty,omitempty"`
|
||||
LeavesQuantity int32 `json:"leavesQty,omitempty"`
|
||||
|
||||
OrderID string `json:"orderID,omitempty"`
|
||||
|
||||
// OrderQty - [Optional] order quantity in units of the instrument
|
||||
// OrderQuantity - [Optional] order quantity in units of the instrument
|
||||
// (i.e. contracts).
|
||||
OrderQty int32 `json:"orderQty,omitempty"`
|
||||
|
||||
@@ -377,15 +377,15 @@ type OrderAmendParams struct {
|
||||
// 'LimitIfTouched' orders.
|
||||
Price float64 `json:"price,omitempty"`
|
||||
|
||||
// SimpleLeavesQty - [Optional] leaves quantity in units of the underlying
|
||||
// SimpleLeavesQuantity - [Optional] leaves quantity in units of the underlying
|
||||
// instrument (i.e. Bitcoin). Useful for amending partially filled orders.
|
||||
SimpleLeavesQty float64 `json:"simpleLeavesQty,omitempty"`
|
||||
SimpleLeavesQuantity float64 `json:"simpleLeavesQty,omitempty"`
|
||||
|
||||
// SimpleOrderQty - [Optional] order quantity in units of the underlying
|
||||
// SimpleOrderQuantity - [Optional] order quantity in units of the underlying
|
||||
// instrument (i.e. Bitcoin).
|
||||
SimpleOrderQty float64 `json:"simpleOrderQty,omitempty"`
|
||||
SimpleOrderQuantity float64 `json:"simpleOrderQty,omitempty"`
|
||||
|
||||
// StopPx - [Optional] trigger price for 'Stop', 'StopLimit',
|
||||
// StopPrice - [Optional] trigger price for 'Stop', 'StopLimit',
|
||||
// 'MarketIfTouched', and 'LimitIfTouched' orders. Use a price below the
|
||||
// current price for stop-sell orders and buy-if-touched orders.
|
||||
StopPx float64 `json:"stopPx,omitempty"`
|
||||
@@ -397,7 +397,7 @@ type OrderAmendParams struct {
|
||||
// VerifyData verifies outgoing data sets
|
||||
func (p *OrderAmendParams) VerifyData() error {
|
||||
if p.OrderID == "" {
|
||||
return errors.New("verifydata() OrderNewParams error - OrderID not set")
|
||||
return errors.New("verifydata() OrderNewParams error - ID not set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -415,8 +415,8 @@ func (p *OrderAmendParams) IsNil() bool {
|
||||
|
||||
// OrderCancelParams contains all the parameters to send to the API endpoint
|
||||
type OrderCancelParams struct {
|
||||
// ClOrdID - Client Order ID(s). See POST /order.
|
||||
ClOrdID string `json:"clOrdID,omitempty"`
|
||||
// ClientOrderID - Client Order ID(s). See POST /order.
|
||||
ClientOrderID string `json:"clOrdID,omitempty"`
|
||||
|
||||
// OrderID - Order ID(s).
|
||||
OrderID string `json:"orderID,omitempty"`
|
||||
|
||||
@@ -50,6 +50,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("Bitmex setup error", err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -231,9 +233,9 @@ func TestAmendOrder(t *testing.T) {
|
||||
|
||||
func TestCreateOrder(t *testing.T) {
|
||||
_, err := b.CreateOrder(&OrderNewParams{Symbol: "XBTM15",
|
||||
Price: 219.0,
|
||||
ClOrdID: "mm_bitmex_1a/oemUeQ4CAJZgP3fjHsA",
|
||||
OrderQty: 98})
|
||||
Price: 219.0,
|
||||
ClientOrderID: "mm_bitmex_1a/oemUeQ4CAJZgP3fjHsA",
|
||||
OrderQuantity: 98})
|
||||
if err == nil {
|
||||
t.Error("CreateOrder() Expected error")
|
||||
}
|
||||
@@ -360,7 +362,7 @@ func TestGetStatSummary(t *testing.T) {
|
||||
|
||||
func TestGetTrade(t *testing.T) {
|
||||
_, err := b.GetTrade(&GenericRequestParams{
|
||||
Symbol: "XBTUSD",
|
||||
Symbol: "ETHUSD",
|
||||
StartTime: time.Now().Format(time.RFC3339),
|
||||
Reverse: true})
|
||||
if err != nil {
|
||||
@@ -478,7 +480,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -491,8 +493,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.LTC,
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.LTC,
|
||||
currency.BTC)},
|
||||
}
|
||||
|
||||
@@ -520,11 +522,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.XBT,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := b.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -541,10 +543,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "123456789012345678901234567890123456",
|
||||
ID: "123456789012345678901234567890123456",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
@@ -563,10 +565,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "123456789012345678901234567890123456",
|
||||
ID: "123456789012345678901234567890123456",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := b.CancelAllOrders(orderCancellation)
|
||||
@@ -601,7 +603,7 @@ func TestModifyOrder(t *testing.T) {
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
_, err := b.ModifyOrder(&order.Modify{OrderID: "1337"})
|
||||
_, err := b.ModifyOrder(&order.Modify{ID: "1337"})
|
||||
if err == nil {
|
||||
t.Error("ModifyOrder() error")
|
||||
}
|
||||
@@ -686,9 +688,8 @@ func TestWsAuth(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go b.wsHandleIncomingData()
|
||||
|
||||
go b.wsReadData()
|
||||
err = b.websocketSendAuth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -704,3 +705,229 @@ func TestWsAuth(t *testing.T) {
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
func TestWsPositionUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"table":"position",
|
||||
"action":"update",
|
||||
"data":[{
|
||||
"account":2,"symbol":"ETHUSD","currency":"XBt",
|
||||
"currentTimestamp":"2017-04-04T22:07:42.442Z", "currentQty":1,"markPrice":1136.88,"markValue":-87960,
|
||||
"riskValue":87960,"homeNotional":0.0008796,"posState":"Liquidation","maintMargin":263,
|
||||
"unrealisedGrossPnl":-677,"unrealisedPnl":-677,"unrealisedPnlPcnt":-0.0078,"unrealisedRoePcnt":-0.7756,
|
||||
"simpleQty":0.001,"liquidationPrice":1140.1, "timestamp":"2017-04-04T22:07:45.442Z"
|
||||
}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsInsertExectuionUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"table":"execution",
|
||||
"action":"insert",
|
||||
"data":[{
|
||||
"execID":"0193e879-cb6f-2891-d099-2c4eb40fee21",
|
||||
"orderID":"00000000-0000-0000-0000-000000000000","clOrdID":"","clOrdLinkID":"","account":2,"symbol":"ETHUSD",
|
||||
"side":"Sell","lastQty":1,"lastPx":1134.37,"underlyingLastPx":null,"lastMkt":"XBME",
|
||||
"lastLiquidityInd":"RemovedLiquidity", "simpleOrderQty":null,"orderQty":1,"price":1134.37,"displayQty":null,
|
||||
"stopPx":null,"pegOffsetValue":null,"pegPriceType":"","currency":"USD","settlCurrency":"XBt",
|
||||
"execType":"Trade","ordType":"Limit","timeInForce":"ImmediateOrCancel","execInst":"",
|
||||
"contingencyType":"","exDestination":"XBME","ordStatus":"Filled","triggered":"","workingIndicator":false,
|
||||
"ordRejReason":"","simpleLeavesQty":0,"leavesQty":0,"simpleCumQty":0.001,"cumQty":1,"avgPx":1134.37,
|
||||
"commission":0.00075,"tradePublishIndicator":"DoNotPublishTrade","multiLegReportingType":"SingleSecurity",
|
||||
"text":"Liquidation","trdMatchID":"7f4ab7f6-0006-3234-76f4-ae1385aad00f","execCost":88155,"execComm":66,
|
||||
"homeNotional":-0.00088155,"foreignNotional":1,"transactTime":"2017-04-04T22:07:46.035Z",
|
||||
"timestamp":"2017-04-04T22:07:46.035Z"
|
||||
}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSConnectionHandling(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"info":"Welcome to the BitMEX Realtime API.","version":"1.1.0",
|
||||
"timestamp":"2015-01-18T10:14:06.802Z","docs":"https://www.bitmex.com/app/wsAPI","heartbeatEnabled":false}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSSubscriptionHandling(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"success":true,"subscribe":"trade:ETHUSD",
|
||||
"request":{"op":"subscribe","args":["trade:ETHUSD","instrument:ETHUSD"]}}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSPositionUpdateHandling(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"table":"position",
|
||||
"action":"update",
|
||||
"data":[{
|
||||
"account":2,"symbol":"ETHUSD","currency":"XBt","currentQty":1,
|
||||
"markPrice":1136.88,"posState":"Liquidated","simpleQty":0.001,"liquidationPrice":1140.1,"bankruptPrice":1134.37,
|
||||
"timestamp":"2017-04-04T22:07:46.019Z"
|
||||
}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`{"table":"position",
|
||||
"action":"update",
|
||||
"data":[{
|
||||
"account":2,"symbol":"ETHUSD","currency":"XBt",
|
||||
"deleveragePercentile":null,"rebalancedPnl":1003,"prevRealisedPnl":-1003,"execSellQty":1,
|
||||
"execSellCost":88155,"execQty":0,"execCost":872,"execComm":131,"currentTimestamp":"2017-04-04T22:07:46.140Z",
|
||||
"currentQty":0,"currentCost":872,"currentComm":131,"realisedCost":872,"unrealisedCost":0,"grossExecCost":0,
|
||||
"isOpen":false,"markPrice":null,"markValue":0,"riskValue":0,"homeNotional":0,"foreignNotional":0,"posState":"",
|
||||
"posCost":0,"posCost2":0,"posInit":0,"posComm":0,"posMargin":0,"posMaint":0,"maintMargin":0,
|
||||
"realisedGrossPnl":-872,"realisedPnl":-1003,"unrealisedGrossPnl":0,"unrealisedPnl":0,
|
||||
"unrealisedPnlPcnt":0,"unrealisedRoePcnt":0,"simpleQty":0,"simpleCost":0,"simpleValue":0,"avgCostPrice":null,
|
||||
"avgEntryPrice":null,"breakEvenPrice":null,"marginCallPrice":null,"liquidationPrice":null,"bankruptPrice":null,
|
||||
"timestamp":"2017-04-04T22:07:46.140Z"
|
||||
}]}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSOrderbookHandling(t *testing.T) {
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
pressXToJSON := []byte(`{
|
||||
"table":"orderBookL2_25",
|
||||
"keys":["symbol","id","side"],
|
||||
"types":{"id":"long","price":"float","side":"symbol","size":"long","symbol":"symbol"},
|
||||
"foreignKeys":{"side":"side","symbol":"instrument"},
|
||||
"attributes":{"id":"sorted","symbol":"grouped"},
|
||||
"action":"partial",
|
||||
"data":[
|
||||
{"symbol":"ETHUSD","id":17999992000,"side":"Sell","size":100,"price":80},
|
||||
{"symbol":"ETHUSD","id":17999993000,"side":"Sell","size":20,"price":70},
|
||||
{"symbol":"ETHUSD","id":17999994000,"side":"Sell","size":10,"price":60},
|
||||
{"symbol":"ETHUSD","id":17999995000,"side":"Buy","size":10,"price":50},
|
||||
{"symbol":"ETHUSD","id":17999996000,"side":"Buy","size":20,"price":40},
|
||||
{"symbol":"ETHUSD","id":17999997000,"side":"Buy","size":100,"price":30}
|
||||
]
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"table":"orderBookL2_25",
|
||||
"action":"update",
|
||||
"data":[
|
||||
{"symbol":"ETHUSD","id":17999995000,"side":"Buy","size":5}
|
||||
]
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"table":"orderBookL2_25",
|
||||
"action":"update",
|
||||
"data":[
|
||||
]
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"table":"orderBookL2_25",
|
||||
"action":"delete",
|
||||
"data":[
|
||||
{"symbol":"ETHUSD","id":17999995000,"side":"Buy"}
|
||||
]
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"table":"orderBookL2_25",
|
||||
"action":"delete",
|
||||
"data":[
|
||||
{"symbol":"ETHUSD","id":17999995000,"side":"Buy"}
|
||||
]
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSDeleveragePositionUpdateHandling(t *testing.T) {
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
pressXToJSON := []byte(`{"table":"position",
|
||||
"action":"update",
|
||||
"data":[{
|
||||
"account":2,"symbol":"ETHUSD","currency":"XBt","currentQty":2000,
|
||||
"markPrice":1160.72,"posState":"Deleverage","simpleQty":1.746,"liquidationPrice":1140.1,
|
||||
"timestamp":"2017-04-04T22:16:38.460Z"
|
||||
}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{"table":"position",
|
||||
"action":"update",
|
||||
"data":[{
|
||||
"account":2,"symbol":"ETHUSD","currency":"XBt",
|
||||
"deleveragePercentile":null,"rebalancedPnl":-2171150,"prevRealisedPnl":2172153,"execSellQty":2001,
|
||||
"execSellCost":172394155,"execQty":0,"execCost":-2259128,"execComm":87978,
|
||||
"currentTimestamp":"2017-04-04T22:16:38.547Z","currentQty":0,"currentCost":-2259128,
|
||||
"currentComm":87978,"realisedCost":-2259128,"unrealisedCost":0,"grossExecCost":0,"isOpen":false,
|
||||
"markPrice":null,"markValue":0,"riskValue":0,"homeNotional":0,"foreignNotional":0,"posState":"","posCost":0,
|
||||
"posCost2":0,"posInit":0,"posComm":0,"posMargin":0,"posMaint":0,"maintMargin":0,"realisedGrossPnl":2259128,
|
||||
"realisedPnl":2171150,"unrealisedGrossPnl":0,"unrealisedPnl":0,"unrealisedPnlPcnt":0,"unrealisedRoePcnt":0,
|
||||
"simpleQty":0,"simpleCost":0,"simpleValue":0,"simplePnl":0,"simplePnlPcnt":0,"avgCostPrice":null,
|
||||
"avgEntryPrice":null,"breakEvenPrice":null,"marginCallPrice":null,"liquidationPrice":null,"bankruptPrice":null,
|
||||
"timestamp":"2017-04-04T22:16:38.547Z"
|
||||
}]}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWSDeleverageExecutionInsertHandling(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"table":"execution",
|
||||
"action":"insert",
|
||||
"data":[{
|
||||
"execID":"20ad1ff4-c110-a4f2-dd31-f94eaa0701fd",
|
||||
"orderID":"00000000-0000-0000-0000-000000000000","clOrdID":"","clOrdLinkID":"","account":2,"symbol":"ETHUSD",
|
||||
"side":"Sell","lastQty":2000,"lastPx":1160.72,"underlyingLastPx":null,"lastMkt":"XBME",
|
||||
"lastLiquidityInd":"AddedLiquidity","simpleOrderQty":null,"orderQty":2000,"price":1160.72,"displayQty":null,
|
||||
"stopPx":null,"pegOffsetValue":null,"pegPriceType":"","currency":"USD","settlCurrency":"XBt","execType":"Trade",
|
||||
"ordType":"Limit","timeInForce":"GoodTillCancel","execInst":"","contingencyType":"","exDestination":"XBME",
|
||||
"ordStatus":"Filled","triggered":"","workingIndicator":false,"ordRejReason":"",
|
||||
"simpleLeavesQty":0,"leavesQty":0,"simpleCumQty":1.746,"cumQty":2000,"avgPx":1160.72,"commission":-0.00025,
|
||||
"tradePublishIndicator":"PublishTrade","multiLegReportingType":"SingleSecurity","text":"Deleverage",
|
||||
"trdMatchID":"1e849b8a-7e88-3c67-a93f-cc654d40e8ba","execCost":172306000,"execComm":-43077,
|
||||
"homeNotional":-1.72306,"foreignNotional":2000,"transactTime":"2017-04-04T22:16:38.472Z",
|
||||
"timestamp":"2017-04-04T22:16:38.472Z"
|
||||
}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrades(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"table":"trade","action":"insert","data":[{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.3,"tickDirection":"MinusTick","trdMatchID":"c427f7a0-6b26-1e10-5c4e-1bd74daf2a73","grossValue":2583000,"homeNotional":0.9904912836767037,"foreignNotional":255.84389857369254},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.3,"tickDirection":"ZeroMinusTick","trdMatchID":"95eb9155-b58c-70e9-44b7-34efe50302e0","grossValue":2583000,"homeNotional":0.9904912836767037,"foreignNotional":255.84389857369254},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.3,"tickDirection":"ZeroMinusTick","trdMatchID":"e607c187-f25c-86bc-cb39-8afff7aaf2d9","grossValue":2583000,"homeNotional":0.9904912836767037,"foreignNotional":255.84389857369254},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":17,"price":258.3,"tickDirection":"ZeroMinusTick","trdMatchID":"0f076814-a57d-9a59-8063-ad6b823a80ac","grossValue":439110,"homeNotional":0.1683835182250396,"foreignNotional":43.49346275752773},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.25,"tickDirection":"MinusTick","trdMatchID":"f4ef3dfd-51c4-538f-37c1-e5071ba1c75d","grossValue":2582500,"homeNotional":0.9904912836767037,"foreignNotional":255.79437400950872},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.25,"tickDirection":"ZeroMinusTick","trdMatchID":"81ef136b-8f4a-b1cf-78a8-fffbfa89bf40","grossValue":2582500,"homeNotional":0.9904912836767037,"foreignNotional":255.79437400950872},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.25,"tickDirection":"ZeroMinusTick","trdMatchID":"65a87e8c-7563-34a4-d040-94e8513c5401","grossValue":2582500,"homeNotional":0.9904912836767037,"foreignNotional":255.79437400950872},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":15,"price":258.25,"tickDirection":"ZeroMinusTick","trdMatchID":"1d11a74e-a157-3f33-036d-35a101fba50b","grossValue":387375,"homeNotional":0.14857369255150554,"foreignNotional":38.369156101426306},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":1,"price":258.25,"tickDirection":"ZeroMinusTick","trdMatchID":"40d49df1-f018-f66f-4ca5-31d4997641d7","grossValue":25825,"homeNotional":0.009904912836767036,"foreignNotional":2.5579437400950873},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.2,"tickDirection":"MinusTick","trdMatchID":"36135b51-73e5-c007-362b-a55be5830c6b","grossValue":2582000,"homeNotional":0.9904912836767037,"foreignNotional":255.7448494453249},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.2,"tickDirection":"ZeroMinusTick","trdMatchID":"6ee19edb-99aa-3030-ba63-933ffb347ade","grossValue":2582000,"homeNotional":0.9904912836767037,"foreignNotional":255.7448494453249},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":100,"price":258.2,"tickDirection":"ZeroMinusTick","trdMatchID":"d44be603-cdb8-d676-e3e2-f91fb12b2a70","grossValue":2582000,"homeNotional":0.9904912836767037,"foreignNotional":255.7448494453249},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":5,"price":258.2,"tickDirection":"ZeroMinusTick","trdMatchID":"a14b43b3-50b4-c075-c54d-dfb0165de33d","grossValue":129100,"homeNotional":0.04952456418383518,"foreignNotional":12.787242472266245},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":8,"price":258.2,"tickDirection":"ZeroMinusTick","trdMatchID":"3c30e175-5194-320c-8f8c-01636c2f4a32","grossValue":206560,"homeNotional":0.07923930269413629,"foreignNotional":20.45958795562599},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":50,"price":258.2,"tickDirection":"ZeroMinusTick","trdMatchID":"5b803378-760b-4919-21fc-bfb275d39ace","grossValue":1291000,"homeNotional":0.49524564183835185,"foreignNotional":127.87242472266244},{"timestamp":"2020-02-17T01:35:36.442Z","symbol":"ETHUSD","side":"Sell","size":244,"price":258.2,"tickDirection":"ZeroMinusTick","trdMatchID":"cf57fec1-c444-b9e5-5e2d-4fb643f4fdb7","grossValue":6300080,"homeNotional":2.416798732171157,"foreignNotional":624.0174326465927}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,62 +62,62 @@ type ConnectedUsers struct {
|
||||
|
||||
// Execution Raw Order and Balance Data
|
||||
type Execution struct {
|
||||
Account int64 `json:"account"`
|
||||
AvgPx float64 `json:"avgPx"`
|
||||
ClOrdID string `json:"clOrdID"`
|
||||
ClOrdLinkID string `json:"clOrdLinkID"`
|
||||
Commission float64 `json:"commission"`
|
||||
ContingencyType string `json:"contingencyType"`
|
||||
CumQty int64 `json:"cumQty"`
|
||||
Currency string `json:"currency"`
|
||||
DisplayQty int64 `json:"displayQty"`
|
||||
ExDestination string `json:"exDestination"`
|
||||
ExecComm int64 `json:"execComm"`
|
||||
ExecCost int64 `json:"execCost"`
|
||||
ExecID string `json:"execID"`
|
||||
ExecInst string `json:"execInst"`
|
||||
ExecType string `json:"execType"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
LastLiquidityInd string `json:"lastLiquidityInd"`
|
||||
LastMkt string `json:"lastMkt"`
|
||||
LastPx float64 `json:"lastPx"`
|
||||
LastQty int64 `json:"lastQty"`
|
||||
LeavesQty int64 `json:"leavesQty"`
|
||||
MultiLegReportingType string `json:"multiLegReportingType"`
|
||||
OrdRejReason string `json:"ordRejReason"`
|
||||
OrdStatus string `json:"ordStatus"`
|
||||
OrdType string `json:"ordType"`
|
||||
OrderID string `json:"orderID"`
|
||||
OrderQty int64 `json:"orderQty"`
|
||||
PegOffsetValue float64 `json:"pegOffsetValue"`
|
||||
PegPriceType string `json:"pegPriceType"`
|
||||
Price float64 `json:"price"`
|
||||
SettlCurrency string `json:"settlCurrency"`
|
||||
Side string `json:"side"`
|
||||
SimpleCumQty float64 `json:"simpleCumQty"`
|
||||
SimpleLeavesQty float64 `json:"simpleLeavesQty"`
|
||||
SimpleOrderQty float64 `json:"simpleOrderQty"`
|
||||
StopPx float64 `json:"stopPx"`
|
||||
Symbol string `json:"symbol"`
|
||||
Text string `json:"text"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
TradePublishIndicator string `json:"tradePublishIndicator"`
|
||||
TransactTime string `json:"transactTime"`
|
||||
TrdMatchID string `json:"trdMatchID"`
|
||||
Triggered string `json:"triggered"`
|
||||
UnderlyingLastPx float64 `json:"underlyingLastPx"`
|
||||
WorkingIndicator bool `json:"workingIndicator"`
|
||||
Account int64 `json:"account"`
|
||||
AvgPx float64 `json:"avgPx"`
|
||||
ClOrdID string `json:"clOrdID"`
|
||||
ClOrdLinkID string `json:"clOrdLinkID"`
|
||||
Commission float64 `json:"commission"`
|
||||
ContingencyType string `json:"contingencyType"`
|
||||
CumQty int64 `json:"cumQty"`
|
||||
Currency string `json:"currency"`
|
||||
DisplayQuantity int64 `json:"displayQty"`
|
||||
ExDestination string `json:"exDestination"`
|
||||
ExecComm int64 `json:"execComm"`
|
||||
ExecCost int64 `json:"execCost"`
|
||||
ExecID string `json:"execID"`
|
||||
ExecInst string `json:"execInst"`
|
||||
ExecType string `json:"execType"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
LastLiquidityInd string `json:"lastLiquidityInd"`
|
||||
LastMkt string `json:"lastMkt"`
|
||||
LastPx float64 `json:"lastPx"`
|
||||
LastQty int64 `json:"lastQty"`
|
||||
LeavesQty int64 `json:"leavesQty"`
|
||||
MultiLegReportingType string `json:"multiLegReportingType"`
|
||||
OrdRejReason string `json:"ordRejReason"`
|
||||
OrdStatus string `json:"ordStatus"`
|
||||
OrdType string `json:"ordType"`
|
||||
OrderID string `json:"orderID"`
|
||||
OrderQty int64 `json:"orderQty"`
|
||||
PegOffsetValue float64 `json:"pegOffsetValue"`
|
||||
PegPriceType string `json:"pegPriceType"`
|
||||
Price float64 `json:"price"`
|
||||
SettlCurrency string `json:"settlCurrency"`
|
||||
Side string `json:"side"`
|
||||
SimpleCumQty float64 `json:"simpleCumQty"`
|
||||
SimpleLeavesQty float64 `json:"simpleLeavesQty"`
|
||||
SimpleOrderQty float64 `json:"simpleOrderQty"`
|
||||
StopPx float64 `json:"stopPx"`
|
||||
Symbol string `json:"symbol"`
|
||||
Text string `json:"text"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TradePublishIndicator string `json:"tradePublishIndicator"`
|
||||
TransactTime string `json:"transactTime"`
|
||||
TrdMatchID string `json:"trdMatchID"`
|
||||
Triggered string `json:"triggered"`
|
||||
UnderlyingLastPx float64 `json:"underlyingLastPx"`
|
||||
WorkingIndicator bool `json:"workingIndicator"`
|
||||
}
|
||||
|
||||
// Funding Swap Funding History
|
||||
type Funding struct {
|
||||
FundingInterval string `json:"fundingInterval"`
|
||||
FundingRate float64 `json:"fundingRate"`
|
||||
FundingRateDaily float64 `json:"fundingRateDaily"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
FundingInterval string `json:"fundingInterval"`
|
||||
FundingRate float64 `json:"fundingRate"`
|
||||
FundingRateDaily float64 `json:"fundingRateDaily"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Instrument Tradeable Contracts, Indices, and History
|
||||
@@ -129,7 +129,7 @@ type Instrument struct {
|
||||
BuyLeg string `json:"buyLeg"`
|
||||
CalcInterval string `json:"calcInterval"`
|
||||
Capped bool `json:"capped"`
|
||||
ClosingTimestamp string `json:"closingTimestamp"`
|
||||
ClosingTimestamp time.Time `json:"closingTimestamp"`
|
||||
Deleverage bool `json:"deleverage"`
|
||||
Expiry string `json:"expiry"`
|
||||
FairBasis float64 `json:"fairBasis"`
|
||||
@@ -142,7 +142,7 @@ type Instrument struct {
|
||||
FundingPremiumSymbol string `json:"fundingPremiumSymbol"`
|
||||
FundingQuoteSymbol string `json:"fundingQuoteSymbol"`
|
||||
FundingRate float64 `json:"fundingRate"`
|
||||
FundingTimestamp string `json:"fundingTimestamp"`
|
||||
FundingTimestamp time.Time `json:"fundingTimestamp"`
|
||||
HasLiquidity bool `json:"hasLiquidity"`
|
||||
HighPrice float64 `json:"highPrice"`
|
||||
ImpactAskPrice float64 `json:"impactAskPrice"`
|
||||
@@ -176,7 +176,7 @@ type Instrument struct {
|
||||
Multiplier int64 `json:"multiplier"`
|
||||
OpenInterest int64 `json:"openInterest"`
|
||||
OpenValue int64 `json:"openValue"`
|
||||
OpeningTimestamp string `json:"openingTimestamp"`
|
||||
OpeningTimestamp time.Time `json:"openingTimestamp"`
|
||||
OptionMultiplier float64 `json:"optionMultiplier"`
|
||||
OptionStrikePcnt float64 `json:"optionStrikePcnt"`
|
||||
OptionStrikePrice float64 `json:"optionStrikePrice"`
|
||||
@@ -192,7 +192,7 @@ type Instrument struct {
|
||||
QuoteCurrency string `json:"quoteCurrency"`
|
||||
QuoteToSettleMultiplier int64 `json:"quoteToSettleMultiplier"`
|
||||
RebalanceInterval string `json:"rebalanceInterval"`
|
||||
RebalanceTimestamp string `json:"rebalanceTimestamp"`
|
||||
RebalanceTimestamp time.Time `json:"rebalanceTimestamp"`
|
||||
Reference string `json:"reference"`
|
||||
ReferenceSymbol string `json:"referenceSymbol"`
|
||||
RelistInterval string `json:"relistInterval"`
|
||||
@@ -233,20 +233,20 @@ type InstrumentInterval struct {
|
||||
|
||||
// IndexComposite index composite
|
||||
type IndexComposite struct {
|
||||
IndexSymbol string `json:"indexSymbol"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
Logged string `json:"logged"`
|
||||
Reference string `json:"reference"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Weight float64 `json:"weight"`
|
||||
IndexSymbol string `json:"indexSymbol"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
Logged string `json:"logged"`
|
||||
Reference string `json:"reference"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Weight float64 `json:"weight"`
|
||||
}
|
||||
|
||||
// Insurance Insurance Fund Data
|
||||
type Insurance struct {
|
||||
Currency string `json:"currency"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
WalletBalance int64 `json:"walletBalance"`
|
||||
Currency string `json:"currency"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
WalletBalance int64 `json:"walletBalance"`
|
||||
}
|
||||
|
||||
// Leaderboard Information on Top Users
|
||||
@@ -286,39 +286,39 @@ type Notification struct {
|
||||
|
||||
// Order Placement, Cancellation, Amending, and History
|
||||
type Order struct {
|
||||
Account int64 `json:"account"`
|
||||
AvgPx float64 `json:"avgPx"`
|
||||
ClOrdID string `json:"clOrdID"`
|
||||
ClOrdLinkID string `json:"clOrdLinkID"`
|
||||
ContingencyType string `json:"contingencyType"`
|
||||
CumQty int64 `json:"cumQty"`
|
||||
Currency string `json:"currency"`
|
||||
DisplayQty int64 `json:"displayQty"`
|
||||
ExDestination string `json:"exDestination"`
|
||||
ExecInst string `json:"execInst"`
|
||||
LeavesQty int64 `json:"leavesQty"`
|
||||
MultiLegReportingType string `json:"multiLegReportingType"`
|
||||
OrdRejReason string `json:"ordRejReason"`
|
||||
OrdStatus string `json:"ordStatus"`
|
||||
OrdType int64 `json:"ordType,string"`
|
||||
OrderID string `json:"orderID"`
|
||||
OrderQty int64 `json:"orderQty"`
|
||||
PegOffsetValue float64 `json:"pegOffsetValue"`
|
||||
PegPriceType string `json:"pegPriceType"`
|
||||
Price float64 `json:"price"`
|
||||
SettlCurrency string `json:"settlCurrency"`
|
||||
Side int64 `json:"side,string"`
|
||||
SimpleCumQty float64 `json:"simpleCumQty"`
|
||||
SimpleLeavesQty float64 `json:"simpleLeavesQty"`
|
||||
SimpleOrderQty float64 `json:"simpleOrderQty"`
|
||||
StopPx float64 `json:"stopPx"`
|
||||
Symbol string `json:"symbol"`
|
||||
Text string `json:"text"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
TransactTime string `json:"transactTime"`
|
||||
Triggered string `json:"triggered"`
|
||||
WorkingIndicator bool `json:"workingIndicator"`
|
||||
Account int64 `json:"account"`
|
||||
AvgPx float64 `json:"avgPx"`
|
||||
ClOrdID string `json:"clOrdID"`
|
||||
ClOrdLinkID string `json:"clOrdLinkID"`
|
||||
ContingencyType string `json:"contingencyType"`
|
||||
CumQty int64 `json:"cumQty"`
|
||||
Currency string `json:"currency"`
|
||||
DisplayQuantity int64 `json:"displayQty"`
|
||||
ExDestination string `json:"exDestination"`
|
||||
ExecInst string `json:"execInst"`
|
||||
LeavesQty int64 `json:"leavesQty"`
|
||||
MultiLegReportingType string `json:"multiLegReportingType"`
|
||||
OrdRejReason string `json:"ordRejReason"`
|
||||
OrdStatus string `json:"ordStatus"`
|
||||
OrdType int64 `json:"ordType,string"`
|
||||
OrderID string `json:"orderID"`
|
||||
OrderQty int64 `json:"orderQty"`
|
||||
PegOffsetValue float64 `json:"pegOffsetValue"`
|
||||
PegPriceType string `json:"pegPriceType"`
|
||||
Price float64 `json:"price"`
|
||||
SettlCurrency string `json:"settlCurrency"`
|
||||
Side int64 `json:"side,string"`
|
||||
SimpleCumQty float64 `json:"simpleCumQty"`
|
||||
SimpleLeavesQty float64 `json:"simpleLeavesQty"`
|
||||
SimpleOrderQty float64 `json:"simpleOrderQty"`
|
||||
StopPx float64 `json:"stopPx"`
|
||||
Symbol string `json:"symbol"`
|
||||
Text string `json:"text"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TransactTime string `json:"transactTime"`
|
||||
Triggered string `json:"triggered"`
|
||||
WorkingIndicator bool `json:"workingIndicator"`
|
||||
}
|
||||
|
||||
// OrderBookL2 contains order book l2
|
||||
@@ -332,120 +332,120 @@ type OrderBookL2 struct {
|
||||
|
||||
// Position Summary of Open and Closed Positions
|
||||
type Position struct {
|
||||
Account int64 `json:"account"`
|
||||
AvgCostPrice float64 `json:"avgCostPrice"`
|
||||
AvgEntryPrice float64 `json:"avgEntryPrice"`
|
||||
BankruptPrice float64 `json:"bankruptPrice"`
|
||||
BreakEvenPrice float64 `json:"breakEvenPrice"`
|
||||
Commission float64 `json:"commission"`
|
||||
CrossMargin bool `json:"crossMargin"`
|
||||
Currency string `json:"currency"`
|
||||
CurrentComm int64 `json:"currentComm"`
|
||||
CurrentCost int64 `json:"currentCost"`
|
||||
CurrentQty int64 `json:"currentQty"`
|
||||
CurrentTimestamp string `json:"currentTimestamp"`
|
||||
DeleveragePercentile float64 `json:"deleveragePercentile"`
|
||||
ExecBuyCost int64 `json:"execBuyCost"`
|
||||
ExecBuyQty int64 `json:"execBuyQty"`
|
||||
ExecComm int64 `json:"execComm"`
|
||||
ExecCost int64 `json:"execCost"`
|
||||
ExecQty int64 `json:"execQty"`
|
||||
ExecSellCost int64 `json:"execSellCost"`
|
||||
ExecSellQty int64 `json:"execSellQty"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
GrossExecCost int64 `json:"grossExecCost"`
|
||||
GrossOpenCost int64 `json:"grossOpenCost"`
|
||||
GrossOpenPremium int64 `json:"grossOpenPremium"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
IndicativeTax int64 `json:"indicativeTax"`
|
||||
IndicativeTaxRate float64 `json:"indicativeTaxRate"`
|
||||
InitMargin int64 `json:"initMargin"`
|
||||
InitMarginReq float64 `json:"initMarginReq"`
|
||||
IsOpen bool `json:"isOpen"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
LastValue int64 `json:"lastValue"`
|
||||
Leverage float64 `json:"leverage"`
|
||||
LiquidationPrice float64 `json:"liquidationPrice"`
|
||||
LongBankrupt int64 `json:"longBankrupt"`
|
||||
MaintMargin int64 `json:"maintMargin"`
|
||||
MaintMarginReq float64 `json:"maintMarginReq"`
|
||||
MarginCallPrice float64 `json:"marginCallPrice"`
|
||||
MarkPrice float64 `json:"markPrice"`
|
||||
MarkValue int64 `json:"markValue"`
|
||||
OpenOrderBuyCost int64 `json:"openOrderBuyCost"`
|
||||
OpenOrderBuyPremium int64 `json:"openOrderBuyPremium"`
|
||||
OpenOrderBuyQty int64 `json:"openOrderBuyQty"`
|
||||
OpenOrderSellCost int64 `json:"openOrderSellCost"`
|
||||
OpenOrderSellPremium int64 `json:"openOrderSellPremium"`
|
||||
OpenOrderSellQty int64 `json:"openOrderSellQty"`
|
||||
OpeningComm int64 `json:"openingComm"`
|
||||
OpeningCost int64 `json:"openingCost"`
|
||||
OpeningQty int64 `json:"openingQty"`
|
||||
OpeningTimestamp string `json:"openingTimestamp"`
|
||||
PosAllowance int64 `json:"posAllowance"`
|
||||
PosComm int64 `json:"posComm"`
|
||||
PosCost int64 `json:"posCost"`
|
||||
PosCost2 int64 `json:"posCost2"`
|
||||
PosCross int64 `json:"posCross"`
|
||||
PosInit int64 `json:"posInit"`
|
||||
PosLoss int64 `json:"posLoss"`
|
||||
PosMaint int64 `json:"posMaint"`
|
||||
PosMargin int64 `json:"posMargin"`
|
||||
PosState string `json:"posState"`
|
||||
PrevClosePrice float64 `json:"prevClosePrice"`
|
||||
PrevRealisedPnl int64 `json:"prevRealisedPnl"`
|
||||
PrevUnrealisedPnl int64 `json:"prevUnrealisedPnl"`
|
||||
QuoteCurrency string `json:"quoteCurrency"`
|
||||
RealisedCost int64 `json:"realisedCost"`
|
||||
RealisedGrossPnl int64 `json:"realisedGrossPnl"`
|
||||
RealisedPnl int64 `json:"realisedPnl"`
|
||||
RealisedTax int64 `json:"realisedTax"`
|
||||
RebalancedPnl int64 `json:"rebalancedPnl"`
|
||||
RiskLimit int64 `json:"riskLimit"`
|
||||
RiskValue int64 `json:"riskValue"`
|
||||
SessionMargin int64 `json:"sessionMargin"`
|
||||
ShortBankrupt int64 `json:"shortBankrupt"`
|
||||
SimpleCost float64 `json:"simpleCost"`
|
||||
SimplePnl float64 `json:"simplePnl"`
|
||||
SimplePnlPcnt float64 `json:"simplePnlPcnt"`
|
||||
SimpleQty float64 `json:"simpleQty"`
|
||||
SimpleValue float64 `json:"simpleValue"`
|
||||
Symbol string `json:"symbol"`
|
||||
TargetExcessMargin int64 `json:"targetExcessMargin"`
|
||||
TaxBase int64 `json:"taxBase"`
|
||||
TaxableMargin int64 `json:"taxableMargin"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Underlying string `json:"underlying"`
|
||||
UnrealisedCost int64 `json:"unrealisedCost"`
|
||||
UnrealisedGrossPnl int64 `json:"unrealisedGrossPnl"`
|
||||
UnrealisedPnl int64 `json:"unrealisedPnl"`
|
||||
UnrealisedPnlPcnt float64 `json:"unrealisedPnlPcnt"`
|
||||
UnrealisedRoePcnt float64 `json:"unrealisedRoePcnt"`
|
||||
UnrealisedTax int64 `json:"unrealisedTax"`
|
||||
VarMargin int64 `json:"varMargin"`
|
||||
Account int64 `json:"account"`
|
||||
AvgCostPrice float64 `json:"avgCostPrice"`
|
||||
AvgEntryPrice float64 `json:"avgEntryPrice"`
|
||||
BankruptPrice float64 `json:"bankruptPrice"`
|
||||
BreakEvenPrice float64 `json:"breakEvenPrice"`
|
||||
Commission float64 `json:"commission"`
|
||||
CrossMargin bool `json:"crossMargin"`
|
||||
Currency string `json:"currency"`
|
||||
CurrentComm int64 `json:"currentComm"`
|
||||
CurrentCost int64 `json:"currentCost"`
|
||||
CurrentQty int64 `json:"currentQty"`
|
||||
CurrentTimestamp time.Time `json:"currentTimestamp"`
|
||||
DeleveragePercentile float64 `json:"deleveragePercentile"`
|
||||
ExecBuyCost int64 `json:"execBuyCost"`
|
||||
ExecBuyQty int64 `json:"execBuyQty"`
|
||||
ExecComm int64 `json:"execComm"`
|
||||
ExecCost int64 `json:"execCost"`
|
||||
ExecQty int64 `json:"execQty"`
|
||||
ExecSellCost int64 `json:"execSellCost"`
|
||||
ExecSellQty int64 `json:"execSellQty"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
GrossExecCost int64 `json:"grossExecCost"`
|
||||
GrossOpenCost int64 `json:"grossOpenCost"`
|
||||
GrossOpenPremium int64 `json:"grossOpenPremium"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
IndicativeTax int64 `json:"indicativeTax"`
|
||||
IndicativeTaxRate float64 `json:"indicativeTaxRate"`
|
||||
InitMargin int64 `json:"initMargin"`
|
||||
InitMarginReq float64 `json:"initMarginReq"`
|
||||
IsOpen bool `json:"isOpen"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
LastValue int64 `json:"lastValue"`
|
||||
Leverage float64 `json:"leverage"`
|
||||
LiquidationPrice float64 `json:"liquidationPrice"`
|
||||
LongBankrupt int64 `json:"longBankrupt"`
|
||||
MaintMargin int64 `json:"maintMargin"`
|
||||
MaintMarginReq float64 `json:"maintMarginReq"`
|
||||
MarginCallPrice float64 `json:"marginCallPrice"`
|
||||
MarkPrice float64 `json:"markPrice"`
|
||||
MarkValue int64 `json:"markValue"`
|
||||
OpenOrderBuyCost int64 `json:"openOrderBuyCost"`
|
||||
OpenOrderBuyPremium int64 `json:"openOrderBuyPremium"`
|
||||
OpenOrderBuyQty int64 `json:"openOrderBuyQty"`
|
||||
OpenOrderSellCost int64 `json:"openOrderSellCost"`
|
||||
OpenOrderSellPremium int64 `json:"openOrderSellPremium"`
|
||||
OpenOrderSellQty int64 `json:"openOrderSellQty"`
|
||||
OpeningComm int64 `json:"openingComm"`
|
||||
OpeningCost int64 `json:"openingCost"`
|
||||
OpeningQty int64 `json:"openingQty"`
|
||||
OpeningTimestamp time.Time `json:"openingTimestamp"`
|
||||
PosAllowance int64 `json:"posAllowance"`
|
||||
PosComm int64 `json:"posComm"`
|
||||
PosCost int64 `json:"posCost"`
|
||||
PosCost2 int64 `json:"posCost2"`
|
||||
PosCross int64 `json:"posCross"`
|
||||
PosInit int64 `json:"posInit"`
|
||||
PosLoss int64 `json:"posLoss"`
|
||||
PosMaint int64 `json:"posMaint"`
|
||||
PosMargin int64 `json:"posMargin"`
|
||||
PosState string `json:"posState"`
|
||||
PrevClosePrice float64 `json:"prevClosePrice"`
|
||||
PrevRealisedPnl int64 `json:"prevRealisedPnl"`
|
||||
PrevUnrealisedPnl int64 `json:"prevUnrealisedPnl"`
|
||||
QuoteCurrency string `json:"quoteCurrency"`
|
||||
RealisedCost int64 `json:"realisedCost"`
|
||||
RealisedGrossPnl int64 `json:"realisedGrossPnl"`
|
||||
RealisedPnl int64 `json:"realisedPnl"`
|
||||
RealisedTax int64 `json:"realisedTax"`
|
||||
RebalancedPnl int64 `json:"rebalancedPnl"`
|
||||
RiskLimit int64 `json:"riskLimit"`
|
||||
RiskValue int64 `json:"riskValue"`
|
||||
SessionMargin int64 `json:"sessionMargin"`
|
||||
ShortBankrupt int64 `json:"shortBankrupt"`
|
||||
SimpleCost float64 `json:"simpleCost"`
|
||||
SimplePnl float64 `json:"simplePnl"`
|
||||
SimplePnlPcnt float64 `json:"simplePnlPcnt"`
|
||||
SimpleQty float64 `json:"simpleQty"`
|
||||
SimpleValue float64 `json:"simpleValue"`
|
||||
Symbol string `json:"symbol"`
|
||||
TargetExcessMargin int64 `json:"targetExcessMargin"`
|
||||
TaxBase int64 `json:"taxBase"`
|
||||
TaxableMargin int64 `json:"taxableMargin"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Underlying string `json:"underlying"`
|
||||
UnrealisedCost int64 `json:"unrealisedCost"`
|
||||
UnrealisedGrossPnl int64 `json:"unrealisedGrossPnl"`
|
||||
UnrealisedPnl int64 `json:"unrealisedPnl"`
|
||||
UnrealisedPnlPcnt float64 `json:"unrealisedPnlPcnt"`
|
||||
UnrealisedRoePcnt float64 `json:"unrealisedRoePcnt"`
|
||||
UnrealisedTax int64 `json:"unrealisedTax"`
|
||||
VarMargin int64 `json:"varMargin"`
|
||||
}
|
||||
|
||||
// Quote Best Bid/Offer Snapshots & Historical Bins
|
||||
type Quote struct {
|
||||
AskPrice float64 `json:"askPrice"`
|
||||
AskSize int64 `json:"askSize"`
|
||||
BidPrice float64 `json:"bidPrice"`
|
||||
BidSize int64 `json:"bidSize"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
AskPrice float64 `json:"askPrice"`
|
||||
AskSize int64 `json:"askSize"`
|
||||
BidPrice float64 `json:"bidPrice"`
|
||||
BidSize int64 `json:"bidSize"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Settlement Historical Settlement Data
|
||||
type Settlement struct {
|
||||
Bankrupt int64 `json:"bankrupt"`
|
||||
OptionStrikePrice float64 `json:"optionStrikePrice"`
|
||||
OptionUnderlyingPrice float64 `json:"optionUnderlyingPrice"`
|
||||
SettledPrice float64 `json:"settledPrice"`
|
||||
SettlementType string `json:"settlementType"`
|
||||
Symbol string `json:"symbol"`
|
||||
TaxBase int64 `json:"taxBase"`
|
||||
TaxRate float64 `json:"taxRate"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Bankrupt int64 `json:"bankrupt"`
|
||||
OptionStrikePrice float64 `json:"optionStrikePrice"`
|
||||
OptionUnderlyingPrice float64 `json:"optionUnderlyingPrice"`
|
||||
SettledPrice float64 `json:"settledPrice"`
|
||||
SettlementType string `json:"settlementType"`
|
||||
Symbol string `json:"symbol"`
|
||||
TaxBase int64 `json:"taxBase"`
|
||||
TaxRate float64 `json:"taxRate"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Stats Exchange Statistics
|
||||
@@ -479,16 +479,16 @@ type StatsUSD struct {
|
||||
|
||||
// Trade Individual & Bucketed Trades
|
||||
type Trade struct {
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
GrossValue int64 `json:"grossValue"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
Price float64 `json:"price"`
|
||||
Side string `json:"side"`
|
||||
Size int64 `json:"size"`
|
||||
Symbol string `json:"symbol"`
|
||||
TickDirection string `json:"tickDirection"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
TrdMatchID string `json:"trdMatchID"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
GrossValue int64 `json:"grossValue"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
Price float64 `json:"price"`
|
||||
Side string `json:"side"`
|
||||
Size int64 `json:"size"`
|
||||
Symbol string `json:"symbol"`
|
||||
TickDirection string `json:"tickDirection"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TrdMatchID string `json:"trdMatchID"`
|
||||
}
|
||||
|
||||
// User Account Operations
|
||||
@@ -544,37 +544,37 @@ type UserPreferences struct {
|
||||
|
||||
// AffiliateStatus affiliate Status details
|
||||
type AffiliateStatus struct {
|
||||
Account int64 `json:"account"`
|
||||
Currency string `json:"currency"`
|
||||
ExecComm int64 `json:"execComm"`
|
||||
ExecTurnover int64 `json:"execTurnover"`
|
||||
PayoutPcnt float64 `json:"payoutPcnt"`
|
||||
PendingPayout int64 `json:"pendingPayout"`
|
||||
PrevComm int64 `json:"prevComm"`
|
||||
PrevPayout int64 `json:"prevPayout"`
|
||||
PrevTimestamp string `json:"prevTimestamp"`
|
||||
PrevTurnover int64 `json:"prevTurnover"`
|
||||
ReferrerAccount float64 `json:"referrerAccount"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
TotalComm int64 `json:"totalComm"`
|
||||
TotalReferrals int64 `json:"totalReferrals"`
|
||||
TotalTurnover int64 `json:"totalTurnover"`
|
||||
Account int64 `json:"account"`
|
||||
Currency string `json:"currency"`
|
||||
ExecComm int64 `json:"execComm"`
|
||||
ExecTurnover int64 `json:"execTurnover"`
|
||||
PayoutPcnt float64 `json:"payoutPcnt"`
|
||||
PendingPayout int64 `json:"pendingPayout"`
|
||||
PrevComm int64 `json:"prevComm"`
|
||||
PrevPayout int64 `json:"prevPayout"`
|
||||
PrevTimestamp time.Time `json:"prevTimestamp"`
|
||||
PrevTurnover int64 `json:"prevTurnover"`
|
||||
ReferrerAccount float64 `json:"referrerAccount"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TotalComm int64 `json:"totalComm"`
|
||||
TotalReferrals int64 `json:"totalReferrals"`
|
||||
TotalTurnover int64 `json:"totalTurnover"`
|
||||
}
|
||||
|
||||
// TransactionInfo Information
|
||||
type TransactionInfo struct {
|
||||
Account int64 `json:"account"`
|
||||
Address string `json:"address"`
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Fee int64 `json:"fee"`
|
||||
Text string `json:"text"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
TransactID string `json:"transactID"`
|
||||
TransactStatus string `json:"transactStatus"`
|
||||
TransactTime string `json:"transactTime"`
|
||||
TransactType string `json:"transactType"`
|
||||
Tx string `json:"tx"`
|
||||
Account int64 `json:"account"`
|
||||
Address string `json:"address"`
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Fee int64 `json:"fee"`
|
||||
Text string `json:"text"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TransactID string `json:"transactID"`
|
||||
TransactStatus string `json:"transactStatus"`
|
||||
TransactTime string `json:"transactTime"`
|
||||
TransactType string `json:"transactType"`
|
||||
Tx string `json:"tx"`
|
||||
}
|
||||
|
||||
// UserCommission user commission
|
||||
@@ -595,47 +595,47 @@ type ConfirmEmail struct {
|
||||
|
||||
// UserMargin margin information
|
||||
type UserMargin struct {
|
||||
Account int64 `json:"account"`
|
||||
Action string `json:"action"`
|
||||
Amount int64 `json:"amount"`
|
||||
AvailableMargin int64 `json:"availableMargin"`
|
||||
Commission float64 `json:"commission"`
|
||||
ConfirmedDebit int64 `json:"confirmedDebit"`
|
||||
Currency string `json:"currency"`
|
||||
ExcessMargin int64 `json:"excessMargin"`
|
||||
ExcessMarginPcnt float64 `json:"excessMarginPcnt"`
|
||||
GrossComm int64 `json:"grossComm"`
|
||||
GrossExecCost int64 `json:"grossExecCost"`
|
||||
GrossLastValue int64 `json:"grossLastValue"`
|
||||
GrossMarkValue int64 `json:"grossMarkValue"`
|
||||
GrossOpenCost int64 `json:"grossOpenCost"`
|
||||
GrossOpenPremium int64 `json:"grossOpenPremium"`
|
||||
IndicativeTax int64 `json:"indicativeTax"`
|
||||
InitMargin int64 `json:"initMargin"`
|
||||
MaintMargin int64 `json:"maintMargin"`
|
||||
MarginBalance int64 `json:"marginBalance"`
|
||||
MarginBalancePcnt float64 `json:"marginBalancePcnt"`
|
||||
MarginLeverage float64 `json:"marginLeverage"`
|
||||
MarginUsedPcnt float64 `json:"marginUsedPcnt"`
|
||||
PendingCredit int64 `json:"pendingCredit"`
|
||||
PendingDebit int64 `json:"pendingDebit"`
|
||||
PrevRealisedPnl int64 `json:"prevRealisedPnl"`
|
||||
PrevState string `json:"prevState"`
|
||||
PrevUnrealisedPnl int64 `json:"prevUnrealisedPnl"`
|
||||
RealisedPnl int64 `json:"realisedPnl"`
|
||||
RiskLimit int64 `json:"riskLimit"`
|
||||
RiskValue int64 `json:"riskValue"`
|
||||
SessionMargin int64 `json:"sessionMargin"`
|
||||
State string `json:"state"`
|
||||
SyntheticMargin int64 `json:"syntheticMargin"`
|
||||
TargetExcessMargin int64 `json:"targetExcessMargin"`
|
||||
TaxableMargin int64 `json:"taxableMargin"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
UnrealisedPnl int64 `json:"unrealisedPnl"`
|
||||
UnrealisedProfit int64 `json:"unrealisedProfit"`
|
||||
VarMargin int64 `json:"varMargin"`
|
||||
WalletBalance int64 `json:"walletBalance"`
|
||||
WithdrawableMargin int64 `json:"withdrawableMargin"`
|
||||
Account int64 `json:"account"`
|
||||
Action string `json:"action"`
|
||||
Amount int64 `json:"amount"`
|
||||
AvailableMargin int64 `json:"availableMargin"`
|
||||
Commission float64 `json:"commission"`
|
||||
ConfirmedDebit int64 `json:"confirmedDebit"`
|
||||
Currency string `json:"currency"`
|
||||
ExcessMargin int64 `json:"excessMargin"`
|
||||
ExcessMarginPcnt float64 `json:"excessMarginPcnt"`
|
||||
GrossComm int64 `json:"grossComm"`
|
||||
GrossExecCost int64 `json:"grossExecCost"`
|
||||
GrossLastValue int64 `json:"grossLastValue"`
|
||||
GrossMarkValue int64 `json:"grossMarkValue"`
|
||||
GrossOpenCost int64 `json:"grossOpenCost"`
|
||||
GrossOpenPremium int64 `json:"grossOpenPremium"`
|
||||
IndicativeTax int64 `json:"indicativeTax"`
|
||||
InitMargin int64 `json:"initMargin"`
|
||||
MaintMargin int64 `json:"maintMargin"`
|
||||
MarginBalance int64 `json:"marginBalance"`
|
||||
MarginBalancePcnt float64 `json:"marginBalancePcnt"`
|
||||
MarginLeverage float64 `json:"marginLeverage"`
|
||||
MarginUsedPcnt float64 `json:"marginUsedPcnt"`
|
||||
PendingCredit int64 `json:"pendingCredit"`
|
||||
PendingDebit int64 `json:"pendingDebit"`
|
||||
PrevRealisedPnl int64 `json:"prevRealisedPnl"`
|
||||
PrevState string `json:"prevState"`
|
||||
PrevUnrealisedPnl int64 `json:"prevUnrealisedPnl"`
|
||||
RealisedPnl int64 `json:"realisedPnl"`
|
||||
RiskLimit int64 `json:"riskLimit"`
|
||||
RiskValue int64 `json:"riskValue"`
|
||||
SessionMargin int64 `json:"sessionMargin"`
|
||||
State string `json:"state"`
|
||||
SyntheticMargin int64 `json:"syntheticMargin"`
|
||||
TargetExcessMargin int64 `json:"targetExcessMargin"`
|
||||
TaxableMargin int64 `json:"taxableMargin"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
UnrealisedPnl int64 `json:"unrealisedPnl"`
|
||||
UnrealisedProfit int64 `json:"unrealisedProfit"`
|
||||
VarMargin int64 `json:"varMargin"`
|
||||
WalletBalance int64 `json:"walletBalance"`
|
||||
WithdrawableMargin int64 `json:"withdrawableMargin"`
|
||||
}
|
||||
|
||||
// MinWithdrawalFee minimum withdrawal fee information
|
||||
@@ -647,31 +647,31 @@ type MinWithdrawalFee struct {
|
||||
|
||||
// WalletInfo wallet information
|
||||
type WalletInfo struct {
|
||||
Account int64 `json:"account"`
|
||||
Addr string `json:"addr"`
|
||||
Amount int64 `json:"amount"`
|
||||
ConfirmedDebit int64 `json:"confirmedDebit"`
|
||||
Currency string `json:"currency"`
|
||||
DeltaAmount int64 `json:"deltaAmount"`
|
||||
DeltaDeposited int64 `json:"deltaDeposited"`
|
||||
DeltaTransferIn int64 `json:"deltaTransferIn"`
|
||||
DeltaTransferOut int64 `json:"deltaTransferOut"`
|
||||
DeltaWithdrawn int64 `json:"deltaWithdrawn"`
|
||||
Deposited int64 `json:"deposited"`
|
||||
PendingCredit int64 `json:"pendingCredit"`
|
||||
PendingDebit int64 `json:"pendingDebit"`
|
||||
PrevAmount int64 `json:"prevAmount"`
|
||||
PrevDeposited int64 `json:"prevDeposited"`
|
||||
PrevTimestamp string `json:"prevTimestamp"`
|
||||
PrevTransferIn int64 `json:"prevTransferIn"`
|
||||
PrevTransferOut int64 `json:"prevTransferOut"`
|
||||
PrevWithdrawn int64 `json:"prevWithdrawn"`
|
||||
Script string `json:"script"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
TransferIn int64 `json:"transferIn"`
|
||||
TransferOut int64 `json:"transferOut"`
|
||||
WithdrawalLock []string `json:"withdrawalLock"`
|
||||
Withdrawn int64 `json:"withdrawn"`
|
||||
Account int64 `json:"account"`
|
||||
Addr string `json:"addr"`
|
||||
Amount int64 `json:"amount"`
|
||||
ConfirmedDebit int64 `json:"confirmedDebit"`
|
||||
Currency string `json:"currency"`
|
||||
DeltaAmount int64 `json:"deltaAmount"`
|
||||
DeltaDeposited int64 `json:"deltaDeposited"`
|
||||
DeltaTransferIn int64 `json:"deltaTransferIn"`
|
||||
DeltaTransferOut int64 `json:"deltaTransferOut"`
|
||||
DeltaWithdrawn int64 `json:"deltaWithdrawn"`
|
||||
Deposited int64 `json:"deposited"`
|
||||
PendingCredit int64 `json:"pendingCredit"`
|
||||
PendingDebit int64 `json:"pendingDebit"`
|
||||
PrevAmount int64 `json:"prevAmount"`
|
||||
PrevDeposited int64 `json:"prevDeposited"`
|
||||
PrevTimestamp time.Time `json:"prevTimestamp"`
|
||||
PrevTransferIn int64 `json:"prevTransferIn"`
|
||||
PrevTransferOut int64 `json:"prevTransferOut"`
|
||||
PrevWithdrawn int64 `json:"prevWithdrawn"`
|
||||
Script string `json:"script"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TransferIn int64 `json:"transferIn"`
|
||||
TransferOut int64 `json:"transferOut"`
|
||||
WithdrawalLock []string `json:"withdrawalLock"`
|
||||
Withdrawn int64 `json:"withdrawn"`
|
||||
}
|
||||
|
||||
// orderTypeMap holds order type info based on Bitmex data
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
bitmexWSInsurance = "insurance"
|
||||
bitmexWSLiquidation = "liquidation"
|
||||
bitmexWSOrderbookL2 = "orderBookL2"
|
||||
bitmexWSOrderbookL225 = "orderBookL2_25"
|
||||
bitmexWSOrderbookL10 = "orderBook10"
|
||||
bitmexWSPublicNotifications = "publicNotifications"
|
||||
bitmexWSQuote = "quote"
|
||||
@@ -93,7 +94,7 @@ func (b *Bitmex) WsConnect() error {
|
||||
welcomeResp.Limit.Remaining)
|
||||
}
|
||||
|
||||
go b.wsHandleIncomingData()
|
||||
go b.wsReadData()
|
||||
b.GenerateDefaultSubscriptions()
|
||||
err = b.websocketSendAuth()
|
||||
if err != nil {
|
||||
@@ -103,8 +104,8 @@ func (b *Bitmex) WsConnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsHandleIncomingData services incoming data from the websocket connection
|
||||
func (b *Bitmex) wsHandleIncomingData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *Bitmex) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
@@ -123,203 +124,348 @@ func (b *Bitmex) wsHandleIncomingData() {
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
|
||||
quickCapture := make(map[string]interface{})
|
||||
err = json.Unmarshal(resp.Raw, &quickCapture)
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
var respError WebsocketErrorResponse
|
||||
if _, ok := quickCapture["status"]; ok {
|
||||
err = json.Unmarshal(resp.Raw, &respError)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- errors.New(respError.Error)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := quickCapture["success"]; ok {
|
||||
var decodedResp WebsocketSubscribeResp
|
||||
err := json.Unmarshal(resp.Raw, &decodedResp)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
if decodedResp.Success {
|
||||
b.Websocket.DataHandler <- decodedResp
|
||||
if len(quickCapture) == 3 {
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s websocket: Successfully subscribed to %s",
|
||||
b.Name, decodedResp.Subscribe)
|
||||
}
|
||||
} else {
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s websocket: Successfully authenticated websocket connection",
|
||||
b.Name)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%s websocket error: Unable to subscribe %s",
|
||||
b.Name, decodedResp.Subscribe)
|
||||
} else if _, ok := quickCapture["table"]; ok {
|
||||
var decodedResp WebsocketMainResponse
|
||||
err := json.Unmarshal(resp.Raw, &decodedResp)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
switch decodedResp.Table {
|
||||
case bitmexWSOrderbookL2:
|
||||
var orderbooks OrderBookData
|
||||
err = json.Unmarshal(resp.Raw, &orderbooks)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(orderbooks.Data[0].Symbol)
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.processOrderbook(orderbooks.Data,
|
||||
orderbooks.Action,
|
||||
p,
|
||||
a)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
case bitmexWSTrade:
|
||||
var trades TradeData
|
||||
err = json.Unmarshal(resp.Raw, &trades)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
if trades.Action == bitmexActionInitialData {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range trades.Data {
|
||||
var timestamp time.Time
|
||||
timestamp, err = time.Parse(time.RFC3339, trades.Data[i].Timestamp)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
// TODO: update this to support multiple asset types
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: timestamp,
|
||||
Price: trades.Data[i].Price,
|
||||
Amount: float64(trades.Data[i].Size),
|
||||
CurrencyPair: currency.NewPairFromString(trades.Data[i].Symbol),
|
||||
Exchange: b.Name,
|
||||
AssetType: "CONTRACT",
|
||||
Side: trades.Data[i].Side,
|
||||
}
|
||||
}
|
||||
|
||||
case bitmexWSAnnouncement:
|
||||
var announcement AnnouncementData
|
||||
err = json.Unmarshal(resp.Raw, &announcement)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
if announcement.Action == bitmexActionInitialData {
|
||||
continue
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- announcement.Data
|
||||
case bitmexWSAffiliate:
|
||||
var response WsAffiliateResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSExecution:
|
||||
var response WsExecutionResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSOrder:
|
||||
var response WsOrderResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSMargin:
|
||||
var response WsMarginResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSPosition:
|
||||
var response WsPositionResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSPrivateNotifications:
|
||||
var response WsPrivateNotificationsResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSTransact:
|
||||
var response WsTransactResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSWallet:
|
||||
var response WsWalletResponse
|
||||
err = json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
default:
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%s websocket error: Table unknown - %s",
|
||||
b.Name, decodedResp.Table)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
quickCapture := make(map[string]interface{})
|
||||
err := json.Unmarshal(respRaw, &quickCapture)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respError WebsocketErrorResponse
|
||||
if _, ok := quickCapture["status"]; ok {
|
||||
err = json.Unmarshal(respRaw, &respError)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := quickCapture["success"]; ok {
|
||||
var decodedResp WebsocketSubscribeResp
|
||||
err = json.Unmarshal(respRaw, &decodedResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if decodedResp.Success {
|
||||
if len(quickCapture) == 3 {
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s websocket: Successfully subscribed to %s",
|
||||
b.Name, decodedResp.Subscribe)
|
||||
}
|
||||
} else {
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s websocket: Successfully authenticated websocket connection",
|
||||
b.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%s websocket error: Unable to subscribe %s",
|
||||
b.Name, decodedResp.Subscribe)
|
||||
} else if _, ok := quickCapture["table"]; ok {
|
||||
var decodedResp WebsocketMainResponse
|
||||
err = json.Unmarshal(respRaw, &decodedResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch decodedResp.Table {
|
||||
case bitmexWSOrderbookL2, bitmexWSOrderbookL225, bitmexWSOrderbookL10:
|
||||
var orderbooks OrderBookData
|
||||
err = json.Unmarshal(respRaw, &orderbooks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(orderbooks.Data) == 0 {
|
||||
return fmt.Errorf("%s - Empty orderbook data received: %s", b.Name, respRaw)
|
||||
}
|
||||
p := currency.NewPairFromString(orderbooks.Data[0].Symbol)
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.processOrderbook(orderbooks.Data,
|
||||
orderbooks.Action,
|
||||
p,
|
||||
a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case bitmexWSTrade:
|
||||
var trades TradeData
|
||||
err = json.Unmarshal(respRaw, &trades)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if trades.Action == bitmexActionInitialData {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range trades.Data {
|
||||
var a asset.Item
|
||||
p := currency.NewPairFromString(trades.Data[i].Symbol)
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(trades.Data[i].Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: trades.Data[i].Timestamp,
|
||||
Price: trades.Data[i].Price,
|
||||
Amount: float64(trades.Data[i].Size),
|
||||
CurrencyPair: p,
|
||||
Exchange: b.Name,
|
||||
AssetType: a,
|
||||
Side: oSide,
|
||||
}
|
||||
}
|
||||
|
||||
case bitmexWSAnnouncement:
|
||||
var announcement AnnouncementData
|
||||
err = json.Unmarshal(respRaw, &announcement)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if announcement.Action == bitmexActionInitialData {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- announcement.Data
|
||||
case bitmexWSAffiliate:
|
||||
var response WsAffiliateResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSInstrument:
|
||||
// ticker
|
||||
case bitmexWSExecution:
|
||||
// trades of an order
|
||||
var response WsExecutionResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range response.Data {
|
||||
p := currency.NewPairFromString(response.Data[i].Symbol)
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = order.StringToOrderStatus(response.Data[i].OrdStatus)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(response.Data[i].Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Modify{
|
||||
Exchange: b.Name,
|
||||
ID: response.Data[i].OrderID,
|
||||
AccountID: strconv.FormatInt(response.Data[i].Account, 10),
|
||||
AssetType: a,
|
||||
Pair: p,
|
||||
Status: oStatus,
|
||||
Trades: []order.TradeHistory{
|
||||
{
|
||||
Price: response.Data[i].Price,
|
||||
Amount: response.Data[i].OrderQuantity,
|
||||
Exchange: b.Name,
|
||||
TID: response.Data[i].ExecID,
|
||||
Side: oSide,
|
||||
Timestamp: response.Data[i].Timestamp,
|
||||
IsMaker: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
case bitmexWSOrder:
|
||||
var response WsOrderResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch response.Action {
|
||||
case "update", "insert":
|
||||
for x := range response.Data {
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = b.GetRequestFormattedPairAndAssetType(response.Data[x].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(response.Data[x].Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oType order.Type
|
||||
oType, err = order.StringToOrderType(response.Data[x].OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: response.Data[x].Price,
|
||||
Amount: response.Data[x].OrderQuantity,
|
||||
Exchange: b.Name,
|
||||
ID: response.Data[x].OrderID,
|
||||
AccountID: strconv.FormatInt(response.Data[x].Account, 10),
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: response.Data[x].TransactTime,
|
||||
Pair: p,
|
||||
}
|
||||
}
|
||||
case "delete":
|
||||
for x := range response.Data {
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = b.GetRequestFormattedPairAndAssetType(response.Data[x].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(response.Data[x].Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oType order.Type
|
||||
oType, err = order.StringToOrderType(response.Data[x].OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Cancel{
|
||||
Price: response.Data[x].Price,
|
||||
Amount: response.Data[x].OrderQuantity,
|
||||
Exchange: b.Name,
|
||||
ID: response.Data[x].OrderID,
|
||||
AccountID: strconv.FormatInt(response.Data[x].Account, 10),
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: response.Data[x].TransactTime,
|
||||
Pair: p,
|
||||
}
|
||||
}
|
||||
default:
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%s - Unsupported order update %+v", b.Name, response)
|
||||
}
|
||||
case bitmexWSMargin:
|
||||
var response WsMarginResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSPosition:
|
||||
var response WsPositionResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case bitmexWSPrivateNotifications:
|
||||
var response WsPrivateNotificationsResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSTransact:
|
||||
var response WsTransactResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
case bitmexWSWallet:
|
||||
var response WsWalletResponse
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessOrderbook processes orderbook updates
|
||||
func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType asset.Item) error { // nolint: unparam
|
||||
if len(data) < 1 {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package bitmex
|
||||
|
||||
import "time"
|
||||
|
||||
// WebsocketRequest is the main request type
|
||||
type WebsocketRequest struct {
|
||||
Command string `json:"op"`
|
||||
@@ -8,7 +10,7 @@ type WebsocketRequest struct {
|
||||
|
||||
// WebsocketErrorResponse main error response
|
||||
type WebsocketErrorResponse struct {
|
||||
Status int `json:"status"`
|
||||
Status int64 `json:"status"`
|
||||
Error string `json:"error"`
|
||||
Meta interface{} `json:"meta"`
|
||||
Request WebsocketRequest `json:"request"`
|
||||
@@ -51,6 +53,7 @@ type WebsocketMainResponse struct {
|
||||
ID string `json:"id"`
|
||||
Symbol string `json:"symbol"`
|
||||
} `json:"Attributes"`
|
||||
Action string `json:"action,omitempty"`
|
||||
}
|
||||
|
||||
// OrderBookData contains orderbook resp data with action to be taken
|
||||
@@ -97,7 +100,58 @@ type WsOrderResponse struct {
|
||||
ForeignKeys WsOrderResponseForeignKeys `json:"foreignKeys"`
|
||||
Attributes WsOrderResponseAttributes `json:"attributes"`
|
||||
Filter WsOrderResponseFilter `json:"filter"`
|
||||
Data []interface{} `json:"data"`
|
||||
Data []OrderInsertData `json:"data"`
|
||||
}
|
||||
|
||||
// OrderInsertData holds order data from an order response
|
||||
type OrderInsertData struct {
|
||||
WorkingIndicator bool `json:"workingIndicator"`
|
||||
Account int64 `json:"account"`
|
||||
AveragePrice float64 `json:"avgPx"`
|
||||
Commission float64 `json:"commission"`
|
||||
FilledQuantity float64 `json:"cumQty"`
|
||||
DisplayQuantity float64 `json:"displayQty"`
|
||||
ExecComm float64 `json:"execComm"`
|
||||
ExecCost float64 `json:"execCost"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
LastPrice float64 `json:"lastPx"`
|
||||
LastQuantity float64 `json:"lastQty"`
|
||||
LeavesQuantity float64 `json:"leavesQty"`
|
||||
OrderQuantity float64 `json:"orderQty"`
|
||||
PegOffsetValue float64 `json:"pegOffsetValue"`
|
||||
Price float64 `json:"price"`
|
||||
SimpleFilledQuantity float64 `json:"simpleCumQty"`
|
||||
SimpleLeavesQuantity float64 `json:"simpleLeavesQty"`
|
||||
SimpleOrderQuantity float64 `json:"simpleOrderQty"`
|
||||
StopPrice float64 `json:"stopPx"`
|
||||
ExDestination string `json:"exDestination"`
|
||||
ContingencyType string `json:"contingencyType"`
|
||||
Currency string `json:"currency"`
|
||||
ExecutionID string `json:"execID"`
|
||||
ExecutionInstance string `json:"execInst"`
|
||||
ExecutionType string `json:"execType"`
|
||||
LastLiquidityInd string `json:"lastLiquidityInd"`
|
||||
LastMkt string `json:"lastMkt"`
|
||||
UnderlyingLastPrice float64 `json:"underlyingLastPx"`
|
||||
MultiLegReportingType string `json:"multiLegReportingType"`
|
||||
OrderRejectedReason string `json:"ordRejReason"`
|
||||
OrderStatus string `json:"ordStatus"`
|
||||
OrderType string `json:"ordType"`
|
||||
OrderID string `json:"orderID"`
|
||||
PegPriceType string `json:"pegPriceType"`
|
||||
ClientOrderID string `json:"clOrdID"`
|
||||
ClientOrderLinkID string `json:"clOrdLinkID"`
|
||||
Symbol string `json:"symbol"`
|
||||
Text string `json:"text"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TradePublishIndicator string `json:"tradePublishIndicator"`
|
||||
TransactTime time.Time `json:"transactTime"`
|
||||
TradingMatchID string `json:"trdMatchID"`
|
||||
Triggered string `json:"triggered"`
|
||||
SettleCurrency string `json:"settlCurrency"`
|
||||
Side string `json:"side"`
|
||||
}
|
||||
|
||||
// WsOrderResponseAttributes private api data
|
||||
@@ -195,7 +249,57 @@ type WsExecutionResponse struct {
|
||||
ForeignKeys WsExecutionResponseForeignKeys `json:"foreignKeys"`
|
||||
Attributes WsExecutionResponseAttributes `json:"attributes"`
|
||||
Filter WsExecutionResponseFilter `json:"filter"`
|
||||
Data []interface{} `json:"data"`
|
||||
Data []wsExecutionData `json:"data"`
|
||||
}
|
||||
|
||||
type wsExecutionData struct {
|
||||
WorkingIndicator bool `json:"workingIndicator"`
|
||||
Account int64 `json:"account"`
|
||||
AvgPx float64 `json:"avgPx"`
|
||||
Commission float64 `json:"commission"`
|
||||
FilledQuantity float64 `json:"cumQty"`
|
||||
DisplayQuantity float64 `json:"displayQty"`
|
||||
ExecComm float64 `json:"execComm"`
|
||||
ExecCost float64 `json:"execCost"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
LastPx float64 `json:"lastPx"`
|
||||
LastQuantity float64 `json:"lastQty"`
|
||||
LeavesQuantity float64 `json:"leavesQty"`
|
||||
OrderQuantity float64 `json:"orderQty"`
|
||||
PegOffsetValue float64 `json:"pegOffsetValue"`
|
||||
Price float64 `json:"price"`
|
||||
SimpleFilledQuantity float64 `json:"simpleCumQty"`
|
||||
SimpleLeavesQuantity float64 `json:"simpleLeavesQty"`
|
||||
SimpleOrderQuantity float64 `json:"simpleOrderQty"`
|
||||
StopPx float64 `json:"stopPx"`
|
||||
UnderlyingLastPx float64 `json:"underlyingLastPx"`
|
||||
PegPriceType string `json:"pegPriceType"`
|
||||
Symbol string `json:"symbol"`
|
||||
Text string `json:"text"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TradePublishIndicator string `json:"tradePublishIndicator"`
|
||||
TransactTime time.Time `json:"transactTime"`
|
||||
TrdMatchID string `json:"trdMatchID"`
|
||||
Triggered string `json:"triggered"`
|
||||
ClOrdID string `json:"clOrdID"`
|
||||
ClOrdLinkID string `json:"clOrdLinkID"`
|
||||
SettlCurrency string `json:"settlCurrency"`
|
||||
Side string `json:"side"`
|
||||
MultiLegReportingType string `json:"multiLegReportingType"`
|
||||
OrdRejReason string `json:"ordRejReason"`
|
||||
OrdStatus string `json:"ordStatus"`
|
||||
OrdType string `json:"ordType"`
|
||||
OrderID string `json:"orderID"`
|
||||
LastLiquidityInd string `json:"lastLiquidityInd"`
|
||||
LastMkt string `json:"lastMkt"`
|
||||
ExecID string `json:"execID"`
|
||||
ExecInst string `json:"execInst"`
|
||||
ExecType string `json:"execType"`
|
||||
ExDestination string `json:"exDestination"`
|
||||
Currency string `json:"currency"`
|
||||
ContingencyType string `json:"contingencyType"`
|
||||
}
|
||||
|
||||
// WsExecutionResponseAttributes private api data
|
||||
@@ -282,7 +386,7 @@ type WsMarginResponseData struct {
|
||||
ExcessMarginPcnt float64 `json:"excessMarginPcnt"`
|
||||
AvailableMargin float64 `json:"availableMargin"`
|
||||
WithdrawableMargin float64 `json:"withdrawableMargin"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
GrossLastValue float64 `json:"grossLastValue"`
|
||||
Commission interface{} `json:"commission"`
|
||||
}
|
||||
@@ -298,7 +402,58 @@ type WsPositionResponse struct {
|
||||
ForeignKeys WsPositionResponseForeignKeys `json:"foreignKeys"`
|
||||
Attributes WsPositionResponseAttributes `json:"attributes"`
|
||||
Filter WsPositionResponseFilter `json:"filter"`
|
||||
Data []interface{} `json:"data"`
|
||||
Data []wsPositionData `json:"data"`
|
||||
}
|
||||
|
||||
type wsPositionData struct {
|
||||
IsOpen bool `json:"isOpen"`
|
||||
Account int64 `json:"account"`
|
||||
CurrentQuantity float64 `json:"currentQty"`
|
||||
HomeNotional float64 `json:"homeNotional"`
|
||||
LiquidationPrice float64 `json:"liquidationPrice"`
|
||||
MaintMargin float64 `json:"maintMargin"`
|
||||
MarkPrice float64 `json:"markPrice"`
|
||||
MarkValue float64 `json:"markValue"`
|
||||
RiskValue float64 `json:"riskValue"`
|
||||
SimpleQuantity float64 `json:"simpleQty"`
|
||||
UnrealisedGrossPnl float64 `json:"unrealisedGrossPnl"`
|
||||
UnrealisedPnl float64 `json:"unrealisedPnl"`
|
||||
UnrealisedPnlPcnt float64 `json:"unrealisedPnlPcnt"`
|
||||
UnrealisedRoePcnt float64 `json:"unrealisedRoePcnt"`
|
||||
BankruptPrice float64 `json:"bankruptPrice"`
|
||||
AvgCostPrice float64 `json:"avgCostPrice"`
|
||||
AvgEntryPrice float64 `json:"avgEntryPrice"`
|
||||
BreakEvenPrice float64 `json:"breakEvenPrice"`
|
||||
CurrentComm float64 `json:"currentComm"`
|
||||
CurrentCost float64 `json:"currentCost"`
|
||||
DeleveragePercentile float64 `json:"deleveragePercentile"`
|
||||
ExecComm float64 `json:"execComm"`
|
||||
ExecCost float64 `json:"execCost"`
|
||||
ExecQuantity float64 `json:"execQty"`
|
||||
ExecSellCost float64 `json:"execSellCost"`
|
||||
ExecSellQuantity float64 `json:"execSellQty"`
|
||||
ForeignNotional float64 `json:"foreignNotional"`
|
||||
GrossExecCost float64 `json:"grossExecCost"`
|
||||
MarginCallPrice float64 `json:"marginCallPrice"`
|
||||
PosComm float64 `json:"posComm"`
|
||||
PosCost float64 `json:"posCost"`
|
||||
PosCost2 float64 `json:"posCost2"`
|
||||
PosInit float64 `json:"posInit"`
|
||||
PosMaint float64 `json:"posMaint"`
|
||||
PosMargin float64 `json:"posMargin"`
|
||||
PrevRealisedPnl float64 `json:"prevRealisedPnl"`
|
||||
RealisedCost float64 `json:"realisedCost"`
|
||||
RealisedGrossPnl float64 `json:"realisedGrossPnl"`
|
||||
RealisedPnl float64 `json:"realisedPnl"`
|
||||
RebalancedPnl float64 `json:"rebalancedPnl"`
|
||||
SimpleCost float64 `json:"simpleCost"`
|
||||
SimpleValue float64 `json:"simpleValue"`
|
||||
UnrealisedCost float64 `json:"unrealisedCost"`
|
||||
Currency string `json:"currency"`
|
||||
CurrentTimestamp time.Time `json:"currentTimestamp"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
PosState string `json:"posState"`
|
||||
}
|
||||
|
||||
// WsPositionResponseAttributes private api data
|
||||
|
||||
@@ -122,6 +122,8 @@ func (b *Bitmex) SetDefaults() {
|
||||
AuthenticatedEndpoints: true,
|
||||
AccountInfo: true,
|
||||
DeadMansSwitch: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.WithdrawCryptoWithEmail |
|
||||
@@ -222,7 +224,7 @@ func (b *Bitmex) Run() {
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (b *Bitmex) FetchTradablePairs(asset asset.Item) ([]string, error) {
|
||||
func (b *Bitmex) FetchTradablePairs(_ asset.Item) ([]string, error) {
|
||||
marketInfo, err := b.GetActiveInstruments(&GenericRequestParams{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -435,13 +437,13 @@ func (b *Bitmex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
var orderNewParams = OrderNewParams{
|
||||
OrdType: s.OrderSide.String(),
|
||||
Symbol: s.Pair.String(),
|
||||
OrderQty: s.Amount,
|
||||
Side: s.OrderSide.String(),
|
||||
OrderType: s.Side.String(),
|
||||
Symbol: s.Pair.String(),
|
||||
OrderQuantity: s.Amount,
|
||||
Side: s.Side.String(),
|
||||
}
|
||||
|
||||
if s.OrderType == order.Limit {
|
||||
if s.Type == order.Limit {
|
||||
orderNewParams.Price = s.Price
|
||||
}
|
||||
|
||||
@@ -452,7 +454,7 @@ func (b *Bitmex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
if response.OrderID != "" {
|
||||
submitOrderResponse.OrderID = response.OrderID
|
||||
}
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
@@ -469,7 +471,7 @@ func (b *Bitmex) ModifyOrder(action *order.Modify) (string, error) {
|
||||
return "", errors.New("contract amount can not have decimals")
|
||||
}
|
||||
|
||||
params.OrderID = action.OrderID
|
||||
params.OrderID = action.ID
|
||||
params.OrderQty = int32(action.Amount)
|
||||
params.Price = action.Price
|
||||
|
||||
@@ -484,7 +486,7 @@ func (b *Bitmex) ModifyOrder(action *order.Modify) (string, error) {
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *Bitmex) CancelOrder(order *order.Cancel) error {
|
||||
var params = OrderCancelParams{
|
||||
OrderID: order.OrderID,
|
||||
OrderID: order.ID,
|
||||
}
|
||||
_, err := b.CancelOrders(¶ms)
|
||||
return err
|
||||
@@ -587,18 +589,18 @@ func (b *Bitmex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orderSide := orderSideMap[resp[i].Side]
|
||||
orderType := orderTypeMap[resp[i].OrdType]
|
||||
if orderType == "" {
|
||||
orderType = order.Unknown
|
||||
orderType = order.UnknownType
|
||||
}
|
||||
|
||||
orderDetail := order.Detail{
|
||||
Price: resp[i].Price,
|
||||
Amount: float64(resp[i].OrderQty),
|
||||
Exchange: b.Name,
|
||||
ID: resp[i].OrderID,
|
||||
OrderSide: orderSide,
|
||||
OrderType: orderType,
|
||||
Status: order.Status(resp[i].OrdStatus),
|
||||
CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol,
|
||||
Price: resp[i].Price,
|
||||
Amount: float64(resp[i].OrderQty),
|
||||
Exchange: b.Name,
|
||||
ID: resp[i].OrderID,
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Status: order.Status(resp[i].OrdStatus),
|
||||
Pair: currency.NewPairWithDelimiter(resp[i].Symbol,
|
||||
resp[i].SettlCurrency,
|
||||
b.GetPairFormat(asset.PerpetualContract, false).Delimiter),
|
||||
}
|
||||
@@ -606,10 +608,10 @@ func (b *Bitmex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -628,18 +630,18 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orderSide := orderSideMap[resp[i].Side]
|
||||
orderType := orderTypeMap[resp[i].OrdType]
|
||||
if orderType == "" {
|
||||
orderType = order.Unknown
|
||||
orderType = order.UnknownType
|
||||
}
|
||||
|
||||
orderDetail := order.Detail{
|
||||
Price: resp[i].Price,
|
||||
Amount: float64(resp[i].OrderQty),
|
||||
Exchange: b.Name,
|
||||
ID: resp[i].OrderID,
|
||||
OrderSide: orderSide,
|
||||
OrderType: orderType,
|
||||
Status: order.Status(resp[i].OrdStatus),
|
||||
CurrencyPair: currency.NewPairWithDelimiter(resp[i].Symbol,
|
||||
Price: resp[i].Price,
|
||||
Amount: float64(resp[i].OrderQty),
|
||||
Exchange: b.Name,
|
||||
ID: resp[i].OrderID,
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Status: order.Status(resp[i].OrdStatus),
|
||||
Pair: currency.NewPairWithDelimiter(resp[i].Symbol,
|
||||
resp[i].SettlCurrency,
|
||||
b.GetPairFormat(asset.PerpetualContract, false).Delimiter),
|
||||
}
|
||||
@@ -647,10 +649,10 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orders = append(orders, orderDetail)
|
||||
}
|
||||
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("Bitstamp setup error", err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
log.Printf(sharedtestvalues.LiveTesting, b.Name, b.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ func TestMain(m *testing.M) {
|
||||
|
||||
b.HTTPClient = newClient
|
||||
b.API.Endpoints.URL = serverDetails + "/api"
|
||||
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -202,7 +202,7 @@ type websocketTradeData struct {
|
||||
SellOrderID int64 `json:"sell_order_id"`
|
||||
AmountStr string `json:"amount_str"`
|
||||
PriceStr string `json:"price_str"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestamp int64 `json:"timestamp,string"`
|
||||
Price float64 `json:"price"`
|
||||
Type int `json:"type"`
|
||||
ID int `json:"id"`
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
@@ -33,31 +34,26 @@ func (b *Bitstamp) WsConnect() error {
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name)
|
||||
}
|
||||
|
||||
err = b.seedOrderBook()
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
|
||||
b.generateDefaultSubscriptions()
|
||||
go b.WsHandleData()
|
||||
go b.wsReadData()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles websocket data from WsReadData
|
||||
func (b *Bitstamp) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *Bitstamp) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
default:
|
||||
resp, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
@@ -65,61 +61,84 @@ func (b *Bitstamp) WsHandleData() {
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
wsResponse := websocketResponse{}
|
||||
err = json.Unmarshal(resp.Raw, &wsResponse)
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
switch wsResponse.Event {
|
||||
case "bts:request_reconnect":
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.Name)
|
||||
}
|
||||
go b.Websocket.Shutdown() // Connection monitor will reconnect
|
||||
|
||||
case "data":
|
||||
wsOrderBookTemp := websocketOrderBookResponse{}
|
||||
err := json.Unmarshal(resp.Raw, &wsOrderBookTemp)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
currencyPair := strings.Split(wsResponse.Channel, "_")
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
|
||||
err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, asset.Spot)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
case "trade":
|
||||
wsTradeTemp := websocketTradeResponse{}
|
||||
|
||||
err := json.Unmarshal(resp.Raw, &wsTradeTemp)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
currencyPair := strings.Split(wsResponse.Channel, "_")
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Price: wsTradeTemp.Data.Price,
|
||||
Amount: wsTradeTemp.Data.Amount,
|
||||
CurrencyPair: p,
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitstamp) wsHandleData(respRaw []byte) error {
|
||||
var wsResponse websocketResponse
|
||||
err := json.Unmarshal(respRaw, &wsResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch wsResponse.Event {
|
||||
case "bts:subscribe":
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket subscription acknowledgement", b.Name)
|
||||
}
|
||||
case "bts:unsubscribe":
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket unsubscribe acknowledgement", b.Name)
|
||||
}
|
||||
case "bts:request_reconnect":
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.Name)
|
||||
}
|
||||
go b.Websocket.Shutdown() // Connection monitor will reconnect
|
||||
case "data":
|
||||
wsOrderBookTemp := websocketOrderBookResponse{}
|
||||
err := json.Unmarshal(respRaw, &wsOrderBookTemp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currencyPair := strings.Split(wsResponse.Channel, "_")
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "trade":
|
||||
wsTradeTemp := websocketTradeResponse{}
|
||||
err := json.Unmarshal(respRaw, &wsTradeTemp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currencyPair := strings.Split(wsResponse.Channel, "_")
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
side := order.Buy
|
||||
if wsTradeTemp.Data.Type == -1 {
|
||||
side = order.Sell
|
||||
}
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(wsTradeTemp.Data.Timestamp, 0),
|
||||
CurrencyPair: p,
|
||||
AssetType: a,
|
||||
Exchange: b.Name,
|
||||
EventType: order.UnknownType,
|
||||
Price: wsTradeTemp.Data.Price,
|
||||
Amount: wsTradeTemp.Data.Amount,
|
||||
Side: side,
|
||||
}
|
||||
case "order_created", "order_deleted", "order_changed":
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket order acknowledgement", b.Name)
|
||||
}
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitstamp) generateDefaultSubscriptions() {
|
||||
var channels = []string{"live_trades_", "order_book_"}
|
||||
enabledCurrencies := b.GetEnabledPairs(asset.Spot)
|
||||
@@ -160,7 +179,6 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair,
|
||||
if len(update.Asks) == 0 && len(update.Bids) == 0 {
|
||||
return errors.New("bitstamp_websocket.go error - no orderbook data")
|
||||
}
|
||||
|
||||
var asks, bids []orderbook.Item
|
||||
for i := range update.Asks {
|
||||
target, err := strconv.ParseFloat(update.Asks[i][0], 64)
|
||||
@@ -168,23 +186,19 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair,
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(update.Asks[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
asks = append(asks, orderbook.Item{Price: target, Amount: amount})
|
||||
}
|
||||
|
||||
for i := range update.Bids {
|
||||
target, err := strconv.ParseFloat(update.Bids[i][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(update.Bids[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
@@ -193,7 +207,6 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair,
|
||||
|
||||
bids = append(bids, orderbook.Item{Price: target, Amount: amount})
|
||||
}
|
||||
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
|
||||
@@ -373,8 +373,8 @@ func (b *Bitstamp) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
buy := s.OrderSide == order.Buy
|
||||
market := s.OrderType == order.Market
|
||||
buy := s.Side == order.Buy
|
||||
market := s.Type == order.Market
|
||||
response, err := b.PlaceOrder(s.Pair.String(),
|
||||
s.Price,
|
||||
s.Amount,
|
||||
@@ -388,7 +388,7 @@ func (b *Bitstamp) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
return submitOrderResponse, nil
|
||||
@@ -402,7 +402,7 @@ func (b *Bitstamp) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *Bitstamp) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -534,10 +534,10 @@ func (b *Bitstamp) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var currPair string
|
||||
if len(req.Currencies) != 1 {
|
||||
if len(req.Pairs) != 1 {
|
||||
currPair = "all"
|
||||
} else {
|
||||
currPair = req.Currencies[0].String()
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
|
||||
resp, err := b.GetOpenOrders(currPair)
|
||||
@@ -559,19 +559,19 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].Amount,
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
OrderType: order.Limit,
|
||||
OrderSide: orderSide,
|
||||
OrderDate: tm,
|
||||
CurrencyPair: currency.NewPairFromString(resp[i].Currency),
|
||||
Exchange: b.Name,
|
||||
Amount: resp[i].Amount,
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
Type: order.Limit,
|
||||
Side: orderSide,
|
||||
Date: tm,
|
||||
Pair: currency.NewPairFromString(resp[i].Currency),
|
||||
Exchange: b.Name,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -579,8 +579,8 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
// Can Limit response to specific order status
|
||||
func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var currPair string
|
||||
if len(req.Currencies) == 1 {
|
||||
currPair = req.Currencies[0].String()
|
||||
if len(req.Pairs) == 1 {
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
resp, err := b.GetUserTransactions(currPair)
|
||||
if err != nil {
|
||||
@@ -601,7 +601,7 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
baseCurrency = currency.XRP
|
||||
default:
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s No base currency found for OrderID '%d'\n",
|
||||
"%s No base currency found for ID '%d'\n",
|
||||
b.Name,
|
||||
resp[i].OrderID)
|
||||
}
|
||||
@@ -632,15 +632,15 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
OrderDate: tm,
|
||||
Exchange: b.Name,
|
||||
CurrencyPair: currPair,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
Date: tm,
|
||||
Exchange: b.Name,
|
||||
Pair: currPair,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -344,11 +344,11 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPairFromString(currPair)},
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPairFromString(currPair)},
|
||||
}
|
||||
|
||||
getOrdersRequest.Currencies[0].Delimiter = "-"
|
||||
getOrdersRequest.Pairs[0].Delimiter = "-"
|
||||
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
@@ -360,7 +360,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := b.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -388,11 +388,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.LTC,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := b.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -409,10 +409,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
@@ -431,10 +431,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := b.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -349,8 +349,8 @@ func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
buy := s.OrderSide == order.Buy
|
||||
if s.OrderType != order.Limit {
|
||||
buy := s.Side == order.Buy
|
||||
if s.Type != order.Limit {
|
||||
return submitOrderResponse,
|
||||
errors.New("limit orders only supported on exchange")
|
||||
}
|
||||
@@ -386,7 +386,7 @@ func (b *Bittrex) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *Bittrex) CancelOrder(order *order.Cancel) error {
|
||||
_, err := b.CancelExistingOrder(order.OrderID)
|
||||
_, err := b.CancelExistingOrder(order.ID)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -468,8 +468,8 @@ func (b *Bittrex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error)
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var currPair string
|
||||
if len(req.Currencies) == 1 {
|
||||
currPair = req.Currencies[0].String()
|
||||
if len(req.Pairs) == 1 {
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
|
||||
resp, err := b.GetOpenOrders(currPair)
|
||||
@@ -497,17 +497,17 @@ func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Amount: resp.Result[i].Quantity,
|
||||
RemainingAmount: resp.Result[i].QuantityRemaining,
|
||||
Price: resp.Result[i].Price,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
ID: resp.Result[i].OrderUUID,
|
||||
Exchange: b.Name,
|
||||
OrderType: orderType,
|
||||
CurrencyPair: pair,
|
||||
Type: orderType,
|
||||
Pair: pair,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -515,8 +515,8 @@ func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
// Can Limit response to specific order status
|
||||
func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var currPair string
|
||||
if len(req.Currencies) == 1 {
|
||||
currPair = req.Currencies[0].String()
|
||||
if len(req.Pairs) == 1 {
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
|
||||
resp, err := b.GetOrderHistoryForCurrency(currPair)
|
||||
@@ -544,18 +544,18 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Amount: resp.Result[i].Quantity,
|
||||
RemainingAmount: resp.Result[i].QuantityRemaining,
|
||||
Price: resp.Result[i].Price,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
ID: resp.Result[i].OrderUUID,
|
||||
Exchange: b.Name,
|
||||
OrderType: orderType,
|
||||
Type: orderType,
|
||||
Fee: resp.Result[i].Commission,
|
||||
CurrencyPair: pair,
|
||||
Pair: pair,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ const (
|
||||
orderChange = "orderChange"
|
||||
heartbeat = "heartbeat"
|
||||
tick = "tick"
|
||||
wsOB = "orderbook"
|
||||
wsOB = "orderbookUpdate"
|
||||
trade = "trade"
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
var b BTCMarkets
|
||||
@@ -45,6 +46,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
|
||||
err = b.ValidateCredentials()
|
||||
if err != nil {
|
||||
@@ -446,12 +449,12 @@ func TestCancelBatchOrders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccountInfo(t *testing.T) {
|
||||
func TestFetchAccountInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys required but not set, skipping test")
|
||||
}
|
||||
_, err := b.UpdateAccountInfo()
|
||||
_, err := b.FetchAccountInfo()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -464,7 +467,7 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := b.GetOrderHistory(&order.GetOrdersRequest{
|
||||
OrderSide: order.Buy,
|
||||
Side: order.Buy,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -500,3 +503,215 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{ "marketId": "BTC-AUD",
|
||||
"timestamp": "2019-04-08T18:56:17.405Z",
|
||||
"bestBid": "7309.12",
|
||||
"bestAsk": "7326.88",
|
||||
"lastPrice": "7316.81",
|
||||
"volume24h": "299.12936654",
|
||||
"messageType": "tick"
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrade(t *testing.T) {
|
||||
pressXToJSON := []byte(` { "marketId": "BTC-AUD",
|
||||
"timestamp": "2019-04-08T20:54:27.632Z",
|
||||
"tradeId": 3153171493,
|
||||
"price": "7370.11",
|
||||
"volume": "0.10901605",
|
||||
"side": "Ask",
|
||||
"messageType": "trade"
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsFundChange(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"fundtransferId": 276811,
|
||||
"type": "Deposit",
|
||||
"status": "Complete",
|
||||
"timestamp": "2019-04-16T01:38:02.931Z",
|
||||
"amount": "0.001",
|
||||
"currency": "BTC",
|
||||
"fee": "0",
|
||||
"messageType": "fundChange"
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderbookUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{ "marketId": "LTC-AUD",
|
||||
"snapshot": true,
|
||||
"timestamp": "2020-01-08T19:47:13.986Z",
|
||||
"snapshotId": 1578512833978000,
|
||||
"bids":
|
||||
[ [ "99.57", "0.55", 1 ],
|
||||
[ "97.62", "3.20", 2 ],
|
||||
[ "97.07", "0.9", 1 ],
|
||||
[ "96.7", "1.9", 1 ],
|
||||
[ "95.8", "7.0", 1 ] ],
|
||||
"asks":
|
||||
[ [ "100", "3.79", 3 ],
|
||||
[ "101", "6.32", 2 ] ],
|
||||
"messageType": "orderbookUpdate"
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(` { "marketId": "LTC-AUD",
|
||||
"timestamp": "2020-01-08T19:47:24.054Z",
|
||||
"snapshotId": 1578512844045000,
|
||||
"bids": [ ["99.81", "1.2", 1 ], ["95.8", "0", 0 ]],
|
||||
"asks": [ ["100", "3.2", 2 ] ],
|
||||
"messageType": "orderbookUpdate"
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsHeartbeats(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"messageType": "error",
|
||||
"code": 3,
|
||||
"message": "invalid channel names"
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"messageType": "error",
|
||||
"code": 3,
|
||||
"message": "invalid marketIds"
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"messageType": "error",
|
||||
"code": 1,
|
||||
"message": "authentication failed. invalid key"
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrders(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"orderId": 79003,
|
||||
"marketId": "BTC-AUD",
|
||||
"side": "Bid",
|
||||
"type": "Limit",
|
||||
"openVolume": "1",
|
||||
"status": "Placed",
|
||||
"triggerStatus": "",
|
||||
"trades": [],
|
||||
"timestamp": "2019-04-08T20:41:19.339Z",
|
||||
"messageType": "orderChange"
|
||||
}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(` {
|
||||
"orderId": 79033,
|
||||
"marketId": "BTC-AUD",
|
||||
"side": "Bid",
|
||||
"type": "Limit",
|
||||
"openVolume": "0",
|
||||
"status": "Fully Matched",
|
||||
"triggerStatus": "",
|
||||
"trades": [{
|
||||
"tradeId":31727,
|
||||
"price":"0.1634",
|
||||
"volume":"10",
|
||||
"fee":"0.001",
|
||||
"liquidityType":"Taker"
|
||||
}],
|
||||
"timestamp": "2019-04-08T20:50:39.658Z",
|
||||
"messageType": "orderChange"
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(` {
|
||||
"orderId": 79003,
|
||||
"marketId": "BTC-AUD",
|
||||
"side": "Bid",
|
||||
"type": "Limit",
|
||||
"openVolume": "1",
|
||||
"status": "Cancelled",
|
||||
"triggerStatus": "",
|
||||
"trades": [],
|
||||
"timestamp": "2019-04-08T20:41:41.857Z",
|
||||
"messageType": "orderChange"
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(` {
|
||||
"orderId": 79003,
|
||||
"marketId": "BTC-AUD",
|
||||
"side": "Bid",
|
||||
"type": "Limit",
|
||||
"openVolume": "1",
|
||||
"status": "Partially Matched",
|
||||
"triggerStatus": "",
|
||||
"trades": [{
|
||||
"tradeId":31927,
|
||||
"price":"0.1634",
|
||||
"volume":"5",
|
||||
"fee":"0.001",
|
||||
"liquidityType":"Taker"
|
||||
}],
|
||||
"timestamp": "2019-04-08T20:41:41.857Z",
|
||||
"messageType": "orderChange"
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(` {
|
||||
"orderId": 7903,
|
||||
"marketId": "BTC-AUD",
|
||||
"side": "Bid",
|
||||
"type": "Limit",
|
||||
"openVolume": "1.2",
|
||||
"status": "Placed",
|
||||
"triggerStatus": "Triggered",
|
||||
"trades": [],
|
||||
"timestamp": "2019-04-08T20:41:41.857Z",
|
||||
"messageType": "orderChange"
|
||||
}`)
|
||||
err = b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,11 +379,12 @@ type WsTrade struct {
|
||||
|
||||
// WsOrderbook message received for orderbook data
|
||||
type WsOrderbook struct {
|
||||
Currency string `json:"marketId"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Bids [][]string `json:"bids"`
|
||||
Asks [][]string `json:"asks"`
|
||||
MessageType string `json:"messageType"`
|
||||
Currency string `json:"marketId"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Bids [][]interface{} `json:"bids"`
|
||||
Asks [][]interface{} `json:"asks"`
|
||||
MessageType string `json:"messageType"`
|
||||
Snapshot bool `json:"snapshot"`
|
||||
}
|
||||
|
||||
// WsFundTransfer stores fund transfer data for websocket
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -38,20 +39,16 @@ func (b *BTCMarkets) WsConnect() error {
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name)
|
||||
}
|
||||
go b.WsHandleData()
|
||||
go b.wsReadData()
|
||||
if b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
b.createChannels()
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
}
|
||||
b.generateDefaultSubscriptions()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles websocket data from WsReadData
|
||||
func (b *BTCMarkets) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *BTCMarkets) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
@@ -68,149 +65,223 @@ func (b *BTCMarkets) WsHandleData() {
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
var wsResponse WsMessageType
|
||||
err = json.Unmarshal(resp.Raw, &wsResponse)
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
switch wsResponse.MessageType {
|
||||
case heartbeat:
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.Name, resp.Raw)
|
||||
}
|
||||
case wsOB:
|
||||
var ob WsOrderbook
|
||||
err := json.Unmarshal(resp.Raw, &ob)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(ob.Currency)
|
||||
var bids, asks []orderbook.Item
|
||||
for x := range ob.Bids {
|
||||
var price, amount float64
|
||||
price, err = strconv.ParseFloat(ob.Bids[x][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
amount, err = strconv.ParseFloat(ob.Bids[x][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
for x := range ob.Asks {
|
||||
var price, amount float64
|
||||
price, err = strconv.ParseFloat(ob.Asks[x][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
amount, err = strconv.ParseFloat(ob.Asks[x][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
||||
Pair: p,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
LastUpdated: ob.Timestamp,
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: b.Name,
|
||||
})
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
case trade:
|
||||
var trade WsTrade
|
||||
err := json.Unmarshal(resp.Raw, &trade)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
p := currency.NewPairFromString(trade.Currency)
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: trade.Timestamp,
|
||||
CurrencyPair: p,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
Price: trade.Price,
|
||||
Amount: trade.Volume,
|
||||
Side: order.SideUnknown.String(),
|
||||
EventType: order.Unknown.String(),
|
||||
}
|
||||
case tick:
|
||||
var tick WsTick
|
||||
err := json.Unmarshal(resp.Raw, &tick)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(tick.Currency)
|
||||
|
||||
b.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: b.Name,
|
||||
Volume: tick.Volume,
|
||||
High: tick.High24,
|
||||
Low: tick.Low24h,
|
||||
Bid: tick.Bid,
|
||||
Ask: tick.Ask,
|
||||
Last: tick.Last,
|
||||
LastUpdated: tick.Timestamp,
|
||||
AssetType: asset.Spot,
|
||||
Pair: p,
|
||||
}
|
||||
case fundChange:
|
||||
var transferData WsFundTransfer
|
||||
err := json.Unmarshal(resp.Raw, &transferData)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- transferData
|
||||
case orderChange:
|
||||
var orderData WsOrderChange
|
||||
err := json.Unmarshal(resp.Raw, &orderData)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- orderData
|
||||
case "error":
|
||||
var wsErr WsError
|
||||
err := json.Unmarshal(resp.Raw, &wsErr)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v websocket error. Code: %v Message: %v", b.Name, wsErr.Code, wsErr.Message)
|
||||
default:
|
||||
b.Websocket.DataHandler <- fmt.Errorf("%v Unhandled websocket message %s", b.Name, resp.Raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
var wsResponse WsMessageType
|
||||
err := json.Unmarshal(respRaw, &wsResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch wsResponse.MessageType {
|
||||
case heartbeat:
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.Name, respRaw)
|
||||
}
|
||||
case wsOB:
|
||||
var ob WsOrderbook
|
||||
err := json.Unmarshal(respRaw, &ob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(ob.Currency)
|
||||
var bids, asks []orderbook.Item
|
||||
for x := range ob.Bids {
|
||||
var price, amount float64
|
||||
price, err = strconv.ParseFloat(ob.Bids[x][0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount, err = strconv.ParseFloat(ob.Bids[x][1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
OrderCount: int64(ob.Bids[x][2].(float64)),
|
||||
})
|
||||
}
|
||||
for x := range ob.Asks {
|
||||
var price, amount float64
|
||||
price, err = strconv.ParseFloat(ob.Asks[x][0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount, err = strconv.ParseFloat(ob.Asks[x][1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
OrderCount: int64(ob.Asks[x][2].(float64)),
|
||||
})
|
||||
}
|
||||
if ob.Snapshot {
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
||||
Pair: p,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
LastUpdated: ob.Timestamp,
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: b.Name,
|
||||
})
|
||||
} else {
|
||||
err = b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
UpdateTime: ob.Timestamp,
|
||||
Asset: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: p,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
case trade:
|
||||
var trade WsTrade
|
||||
err := json.Unmarshal(respRaw, &trade)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := currency.NewPairFromString(trade.Currency)
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: trade.Timestamp,
|
||||
CurrencyPair: p,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
Price: trade.Price,
|
||||
Amount: trade.Volume,
|
||||
Side: order.UnknownSide,
|
||||
EventType: order.UnknownType,
|
||||
}
|
||||
case tick:
|
||||
var tick WsTick
|
||||
err := json.Unmarshal(respRaw, &tick)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(tick.Currency)
|
||||
|
||||
b.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: b.Name,
|
||||
Volume: tick.Volume,
|
||||
High: tick.High24,
|
||||
Low: tick.Low24h,
|
||||
Bid: tick.Bid,
|
||||
Ask: tick.Ask,
|
||||
Last: tick.Last,
|
||||
LastUpdated: tick.Timestamp,
|
||||
AssetType: asset.Spot,
|
||||
Pair: p,
|
||||
}
|
||||
case fundChange:
|
||||
var transferData WsFundTransfer
|
||||
err := json.Unmarshal(respRaw, &transferData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- transferData
|
||||
case orderChange:
|
||||
var orderData WsOrderChange
|
||||
err := json.Unmarshal(respRaw, &orderData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
originalAmount := orderData.OpenVolume
|
||||
var price float64
|
||||
var trades []order.TradeHistory
|
||||
var orderID = strconv.FormatInt(orderData.OrderID, 10)
|
||||
for x := range orderData.Trades {
|
||||
var isMaker bool
|
||||
if orderData.Trades[x].LiquidityType == "Maker" {
|
||||
isMaker = true
|
||||
}
|
||||
trades = append(trades, order.TradeHistory{
|
||||
Price: orderData.Trades[x].Price,
|
||||
Amount: orderData.Trades[x].Volume,
|
||||
Fee: orderData.Trades[x].Fee,
|
||||
Exchange: b.Name,
|
||||
TID: strconv.FormatInt(orderData.Trades[x].TradeID, 10),
|
||||
IsMaker: isMaker,
|
||||
})
|
||||
price = orderData.Trades[x].Price
|
||||
originalAmount += orderData.Trades[x].Volume
|
||||
}
|
||||
oType, err := order.StringToOrderType(orderData.OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oSide, err := order.StringToOrderSide(orderData.Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oStatus, err := order.StringToOrderStatus(orderData.Status)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
p := currency.NewPairFromString(orderData.MarketID)
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: price,
|
||||
Amount: originalAmount,
|
||||
RemainingAmount: orderData.OpenVolume,
|
||||
Exchange: b.Name,
|
||||
ID: orderID,
|
||||
ClientID: b.API.Credentials.ClientID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: orderData.Timestamp,
|
||||
Trades: trades,
|
||||
Pair: p,
|
||||
}
|
||||
case "error":
|
||||
var wsErr WsError
|
||||
err := json.Unmarshal(respRaw, &wsErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%v websocket error. Code: %v Message: %v", b.Name, wsErr.Code, wsErr.Message)
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BTCMarkets) generateDefaultSubscriptions() {
|
||||
var channels = []string{tick, trade, wsOB}
|
||||
enabledCurrencies := b.GetEnabledPairs(asset.Spot)
|
||||
|
||||
@@ -101,6 +101,8 @@ func (b *BTCMarkets) SetDefaults() {
|
||||
AccountInfo: true,
|
||||
Subscribe: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.AutoWithdrawFiat,
|
||||
@@ -159,6 +161,14 @@ func (b *BTCMarkets) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -370,18 +380,18 @@ func (b *BTCMarkets) SubmitOrder(s *order.Submit) (order.SubmitResponse, error)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if s.OrderSide == order.Sell {
|
||||
s.OrderSide = order.Ask
|
||||
if s.Side == order.Sell {
|
||||
s.Side = order.Ask
|
||||
}
|
||||
if s.OrderSide == order.Buy {
|
||||
s.OrderSide = order.Bid
|
||||
if s.Side == order.Buy {
|
||||
s.Side = order.Bid
|
||||
}
|
||||
|
||||
tempResp, err := b.NewOrder(b.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
s.Price,
|
||||
s.Amount,
|
||||
s.OrderType.String(),
|
||||
s.OrderSide.String(),
|
||||
s.Type.String(),
|
||||
s.Side.String(),
|
||||
s.TriggerPrice,
|
||||
s.TargetAmount,
|
||||
"",
|
||||
@@ -404,7 +414,7 @@ func (b *BTCMarkets) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *BTCMarkets) CancelOrder(o *order.Cancel) error {
|
||||
_, err := b.RemoveOrder(o.OrderID)
|
||||
_, err := b.RemoveOrder(o.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -446,27 +456,27 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
}
|
||||
resp.Exchange = b.Name
|
||||
resp.ID = orderID
|
||||
resp.CurrencyPair = currency.NewPairFromString(o.MarketID)
|
||||
resp.Pair = currency.NewPairFromString(o.MarketID)
|
||||
resp.Price = o.Price
|
||||
resp.OrderDate = o.CreationTime
|
||||
resp.Date = o.CreationTime
|
||||
resp.ExecutedAmount = o.Amount - o.OpenAmount
|
||||
resp.OrderSide = order.Bid
|
||||
resp.Side = order.Bid
|
||||
if o.Side == ask {
|
||||
resp.OrderSide = order.Ask
|
||||
resp.Side = order.Ask
|
||||
}
|
||||
switch o.Type {
|
||||
case limit:
|
||||
resp.OrderType = order.Limit
|
||||
resp.Type = order.Limit
|
||||
case market:
|
||||
resp.OrderType = order.Market
|
||||
resp.Type = order.Market
|
||||
case stopLimit:
|
||||
resp.OrderType = order.Stop
|
||||
resp.Type = order.Stop
|
||||
case stop:
|
||||
resp.OrderType = order.Stop
|
||||
resp.Type = order.Stop
|
||||
case takeProfit:
|
||||
resp.OrderType = order.ImmediateOrCancel
|
||||
resp.Type = order.ImmediateOrCancel
|
||||
default:
|
||||
resp.OrderType = order.Unknown
|
||||
resp.Type = order.UnknownType
|
||||
}
|
||||
resp.RemainingAmount = o.OpenAmount
|
||||
switch o.Status {
|
||||
@@ -561,42 +571,42 @@ func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, err
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
allPairs := b.GetEnabledPairs(asset.Spot)
|
||||
for a := range allPairs {
|
||||
req.Currencies = append(req.Currencies,
|
||||
req.Pairs = append(req.Pairs,
|
||||
allPairs[a])
|
||||
}
|
||||
}
|
||||
|
||||
var resp []order.Detail
|
||||
for x := range req.Currencies {
|
||||
tempData, err := b.GetOrders(b.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String(), -1, -1, -1, true)
|
||||
for x := range req.Pairs {
|
||||
tempData, err := b.GetOrders(b.FormatExchangeCurrency(req.Pairs[x], asset.Spot).String(), -1, -1, -1, true)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
for y := range tempData {
|
||||
var tempResp order.Detail
|
||||
tempResp.Exchange = b.Name
|
||||
tempResp.CurrencyPair = req.Currencies[x]
|
||||
tempResp.Pair = req.Pairs[x]
|
||||
tempResp.ID = tempData[y].OrderID
|
||||
tempResp.OrderSide = order.Bid
|
||||
tempResp.Side = order.Bid
|
||||
if tempData[y].Side == ask {
|
||||
tempResp.OrderSide = order.Ask
|
||||
tempResp.Side = order.Ask
|
||||
}
|
||||
tempResp.OrderDate = tempData[y].CreationTime
|
||||
tempResp.Date = tempData[y].CreationTime
|
||||
|
||||
switch tempData[y].Type {
|
||||
case limit:
|
||||
tempResp.OrderType = order.Limit
|
||||
tempResp.Type = order.Limit
|
||||
case market:
|
||||
tempResp.OrderType = order.Market
|
||||
tempResp.Type = order.Market
|
||||
default:
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s unknown order type %s getting order",
|
||||
b.Name,
|
||||
tempData[y].Type)
|
||||
tempResp.OrderType = order.Unknown
|
||||
tempResp.Type = order.UnknownType
|
||||
}
|
||||
switch tempData[y].Status {
|
||||
case orderAccepted:
|
||||
@@ -620,9 +630,9 @@ func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detai
|
||||
resp = append(resp, tempResp)
|
||||
}
|
||||
}
|
||||
order.FilterOrdersByType(&resp, req.OrderType)
|
||||
order.FilterOrdersByType(&resp, req.Type)
|
||||
order.FilterOrdersByTickRange(&resp, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&resp, req.OrderSide)
|
||||
order.FilterOrdersBySide(&resp, req.Side)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -632,7 +642,7 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
var resp []order.Detail
|
||||
var tempResp order.Detail
|
||||
var tempArray []string
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
orders, err := b.GetOrders("", -1, -1, -1, false)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
@@ -641,8 +651,8 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
tempArray = append(tempArray, orders[x].OrderID)
|
||||
}
|
||||
}
|
||||
for y := range req.Currencies {
|
||||
orders, err := b.GetOrders(b.FormatExchangeCurrency(req.Currencies[y], asset.Spot).String(), -1, -1, -1, false)
|
||||
for y := range req.Pairs {
|
||||
orders, err := b.GetOrders(b.FormatExchangeCurrency(req.Pairs[y], asset.Spot).String(), -1, -1, -1, false)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
@@ -674,13 +684,13 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
continue
|
||||
}
|
||||
tempResp.Exchange = b.Name
|
||||
tempResp.CurrencyPair = currency.NewPairFromString(tempData.Orders[c].MarketID)
|
||||
tempResp.OrderSide = order.Bid
|
||||
tempResp.Pair = currency.NewPairFromString(tempData.Orders[c].MarketID)
|
||||
tempResp.Side = order.Bid
|
||||
if tempData.Orders[c].Side == ask {
|
||||
tempResp.OrderSide = order.Ask
|
||||
tempResp.Side = order.Ask
|
||||
}
|
||||
tempResp.ID = tempData.Orders[c].OrderID
|
||||
tempResp.OrderDate = tempData.Orders[c].CreationTime
|
||||
tempResp.Date = tempData.Orders[c].CreationTime
|
||||
tempResp.Price = tempData.Orders[c].Price
|
||||
tempResp.ExecutedAmount = tempData.Orders[c].Amount
|
||||
resp = append(resp, tempResp)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
// Please supply your own keys here to do better tests
|
||||
@@ -43,7 +44,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -163,7 +165,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -178,7 +180,7 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
_, err := b.GetOrderHistory(&getOrdersRequest)
|
||||
if err != nil {
|
||||
@@ -299,11 +301,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 100000,
|
||||
Amount: 0.1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 100000,
|
||||
Amount: 0.1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := b.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -322,10 +324,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
currency.USD.String(),
|
||||
"-")
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "b334ecef-2b42-4998-b8a4-b6b14f6d2671",
|
||||
ID: "b334ecef-2b42-4998-b8a4-b6b14f6d2671",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
if err != nil {
|
||||
@@ -342,10 +344,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
currency.USD.String(),
|
||||
"-")
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
resp, err := b.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -358,3 +360,54 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderbook(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"topic":"orderBookApi:BTC-USD_0","data":{"buyQuote":[{"price":"9272.0","size":"0.077"},{"price":"9271.0","size":"1.122"},{"price":"9270.0","size":"2.548"},{"price":"9267.5","size":"1.015"},{"price":"9265.5","size":"0.930"},{"price":"9265.0","size":"0.475"},{"price":"9264.5","size":"2.216"},{"price":"9264.0","size":"9.709"},{"price":"9263.5","size":"3.667"},{"price":"9263.0","size":"8.481"},{"price":"9262.5","size":"7.660"},{"price":"9262.0","size":"9.689"},{"price":"9261.5","size":"4.213"},{"price":"9261.0","size":"1.491"},{"price":"9260.5","size":"6.264"},{"price":"9260.0","size":"1.690"},{"price":"9259.5","size":"5.718"},{"price":"9259.0","size":"2.706"},{"price":"9258.5","size":"0.192"},{"price":"9258.0","size":"1.592"},{"price":"9257.5","size":"1.749"},{"price":"9257.0","size":"8.104"},{"price":"9256.0","size":"0.161"},{"price":"9252.0","size":"1.544"},{"price":"9249.5","size":"1.462"},{"price":"9247.5","size":"1.833"},{"price":"9247.0","size":"0.168"},{"price":"9245.5","size":"1.941"},{"price":"9244.0","size":"1.423"},{"price":"9243.5","size":"0.175"}],"currency":"USD","sellQuote":[{"price":"9303.5","size":"1.839"},{"price":"9303.0","size":"2.067"},{"price":"9302.0","size":"0.117"},{"price":"9298.5","size":"1.569"},{"price":"9297.0","size":"1.527"},{"price":"9295.0","size":"0.184"},{"price":"9294.0","size":"1.785"},{"price":"9289.0","size":"1.673"},{"price":"9287.5","size":"4.194"},{"price":"9287.0","size":"6.622"},{"price":"9286.5","size":"2.147"},{"price":"9286.0","size":"3.348"},{"price":"9285.5","size":"5.655"},{"price":"9285.0","size":"10.423"},{"price":"9284.5","size":"6.233"},{"price":"9284.0","size":"8.860"},{"price":"9283.5","size":"9.441"},{"price":"9283.0","size":"3.455"},{"price":"9282.5","size":"11.033"},{"price":"9282.0","size":"11.471"},{"price":"9281.5","size":"4.742"},{"price":"9281.0","size":"14.789"},{"price":"9280.5","size":"11.117"},{"price":"9280.0","size":"0.807"},{"price":"9279.5","size":"1.651"},{"price":"9279.0","size":"0.244"},{"price":"9278.5","size":"0.533"},{"price":"9277.0","size":"1.447"},{"price":"9273.0","size":"1.976"},{"price":"9272.5","size":"0.093"}]}}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrades(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"topic":"tradeHistory:BTC-USD","data":[{"amount":0.09,"gain":1,"newest":0,"price":9273.6,"serialId":0,"transactionUnixtime":1580349090693}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderNotification(t *testing.T) {
|
||||
status := []string{"ORDER_INSERTED", "ORDER_CANCELLED", "TRIGGER_INSERTED", "ORDER_FULL_TRANSACTED", "ORDER_PARTIALLY_TRANSACTED", "INSUFFICIENT_BALANCE", "TRIGGER_ACTIVATED", "MARKET_UNAVAILABLE"}
|
||||
for i := range status {
|
||||
pressXToJSON := []byte(`{"topic": "notificationApi","data": [{"symbol": "BTC-USD","orderID": "1234","orderMode": "MODE_BUY","orderType": "TYPE_LIMIT","price": "1","size": "1","status": "` + status[i] + `","timestamp": "1580349090693","type": "STOP","triggerPrice": "1"}]}`)
|
||||
err := b.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusToStandardStatus(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Status
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "ORDER_INSERTED", Result: order.New},
|
||||
{Case: "TRIGGER_INSERTED", Result: order.New},
|
||||
{Case: "ORDER_CANCELLED", Result: order.Cancelled},
|
||||
{Case: "ORDER_FULL_TRANSACTED", Result: order.Filled},
|
||||
{Case: "ORDER_PARTIALLY_TRANSACTED", Result: order.PartiallyFilled},
|
||||
{Case: "TRIGGER_ACTIVATED", Result: order.Active},
|
||||
{Case: "INSUFFICIENT_BALANCE", Result: order.InsufficientBalance},
|
||||
{Case: "MARKET_UNAVAILABLE", Result: order.MarketUnavailable},
|
||||
{Case: "LOL", Result: order.UnknownStatus},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderStatus(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,3 +165,23 @@ type wsTradeHistory struct {
|
||||
Topic string `json:"topic"`
|
||||
Data []wsTradeData `json:"data"`
|
||||
}
|
||||
|
||||
type wsNotification struct {
|
||||
Topic string `json:"topic"`
|
||||
Data []wsOrderUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type wsOrderUpdate struct {
|
||||
OrderID string `json:"orderID"`
|
||||
OrderMode string `json:"orderMode"`
|
||||
OrderType string `json:"orderType"`
|
||||
PegPriceDeviation string `json:"pegPriceDeviation"`
|
||||
Price float64 `json:"price,string"`
|
||||
Size float64 `json:"size,string"`
|
||||
Status string `json:"status"`
|
||||
Stealth string `json:"stealth"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp int64 `json:"timestamp,string"`
|
||||
TriggerPrice float64 `json:"triggerPrice,string"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,14 +38,60 @@ func (b *BTSE) WsConnect() error {
|
||||
MessageType: websocket.PingMessage,
|
||||
Delay: btseWebsocketTimer,
|
||||
})
|
||||
go b.WsHandleData()
|
||||
b.GenerateDefaultSubscriptions()
|
||||
|
||||
go b.wsReadData()
|
||||
if b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
err = b.WsAuthenticate()
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
}
|
||||
|
||||
b.GenerateDefaultSubscriptions()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles read data from websocket connection
|
||||
func (b *BTSE) WsHandleData() {
|
||||
// WsAuthenticate Send an authentication message to receive auth data
|
||||
func (b *BTSE) WsAuthenticate() error {
|
||||
nonce := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
path := "/spotWS" + nonce
|
||||
hmac := crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte((path + nonce)),
|
||||
[]byte(b.API.Credentials.Secret),
|
||||
)
|
||||
sign := crypto.HexEncodeToString(hmac)
|
||||
req := wsSub{
|
||||
Operation: "authKeyExpires",
|
||||
Arguments: []string{b.API.Credentials.Key, nonce, sign},
|
||||
}
|
||||
return b.WebsocketConn.SendJSONMessage(req)
|
||||
}
|
||||
|
||||
func stringToOrderStatus(status string) (order.Status, error) {
|
||||
switch status {
|
||||
case "ORDER_INSERTED", "TRIGGER_INSERTED":
|
||||
return order.New, nil
|
||||
case "ORDER_CANCELLED":
|
||||
return order.Cancelled, nil
|
||||
case "ORDER_FULL_TRANSACTED":
|
||||
return order.Filled, nil
|
||||
case "ORDER_PARTIALLY_TRANSACTED":
|
||||
return order.PartiallyFilled, nil
|
||||
case "TRIGGER_ACTIVATED":
|
||||
return order.Active, nil
|
||||
case "INSUFFICIENT_BALANCE":
|
||||
return order.InsufficientBalance, nil
|
||||
case "MARKET_UNAVAILABLE":
|
||||
return order.MarketUnavailable, nil
|
||||
default:
|
||||
return order.UnknownStatus, errors.New(status + " not recognised as order status")
|
||||
}
|
||||
}
|
||||
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *BTSE) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
@@ -63,106 +110,177 @@ func (b *BTSE) WsHandleData() {
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
|
||||
type Result map[string]interface{}
|
||||
result := Result{}
|
||||
err = json.Unmarshal(resp.Raw, &result)
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(result["topic"].(string), "tradeHistory"):
|
||||
var tradeHistory wsTradeHistory
|
||||
err = json.Unmarshal(resp.Raw, &tradeHistory)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
for x := range tradeHistory.Data {
|
||||
side := order.Buy.String()
|
||||
if tradeHistory.Data[x].Gain == -1 {
|
||||
side = order.Sell.String()
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(0, tradeHistory.Data[x].TransactionTime*int64(time.Millisecond)),
|
||||
CurrencyPair: currency.NewPairFromString(strings.Replace(tradeHistory.Topic, "tradeHistory:", "", 1)),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
Price: tradeHistory.Data[x].Price,
|
||||
Amount: tradeHistory.Data[x].Amount,
|
||||
Side: side,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result["topic"].(string), "orderBookApi"):
|
||||
var t wsOrderBook
|
||||
err = json.Unmarshal(resp.Raw, &t)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
var newOB orderbook.Base
|
||||
var price, amount float64
|
||||
for i := range t.Data.SellQuote {
|
||||
p := strings.Replace(t.Data.SellQuote[i].Price, ",", "", -1)
|
||||
price, err = strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
a := strings.Replace(t.Data.SellQuote[i].Size, ",", "", -1)
|
||||
amount, err = strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
newOB.Asks = append(newOB.Asks, orderbook.Item{
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
for j := range t.Data.BuyQuote {
|
||||
p := strings.Replace(t.Data.BuyQuote[j].Price, ",", "", -1)
|
||||
price, err = strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
a := strings.Replace(t.Data.BuyQuote[j].Size, ",", "", -1)
|
||||
amount, err = strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
newOB.Bids = append(newOB.Bids, orderbook.Item{
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
newOB.AssetType = asset.Spot
|
||||
newOB.Pair = currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")])
|
||||
newOB.ExchangeName = b.Name
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOB)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name}
|
||||
default:
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s: unhandled websocket response: %s", b.Name, resp.Raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BTSE) wsHandleData(respRaw []byte) error {
|
||||
type Result map[string]interface{}
|
||||
var result Result
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case result["topic"] == "notificationApi":
|
||||
var notification wsNotification
|
||||
err = json.Unmarshal(respRaw, ¬ification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range notification.Data {
|
||||
var oType order.Type
|
||||
var oSide order.Side
|
||||
var oStatus order.Status
|
||||
oType, err = order.StringToOrderType(notification.Data[i].Type)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: notification.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oSide, err = order.StringToOrderSide(notification.Data[i].OrderMode)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: notification.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oStatus, err = stringToOrderStatus(notification.Data[i].Status)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: notification.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
p := currency.NewPairFromString(notification.Data[i].Symbol)
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: notification.Data[i].Price,
|
||||
Amount: notification.Data[i].Size,
|
||||
TriggerPrice: notification.Data[i].TriggerPrice,
|
||||
Exchange: b.Name,
|
||||
ID: notification.Data[i].OrderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: time.Unix(0, notification.Data[i].Timestamp*int64(time.Millisecond)),
|
||||
Pair: p,
|
||||
}
|
||||
}
|
||||
|
||||
case strings.Contains(result["topic"].(string), "tradeHistory"):
|
||||
var tradeHistory wsTradeHistory
|
||||
err = json.Unmarshal(respRaw, &tradeHistory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for x := range tradeHistory.Data {
|
||||
side := order.Buy
|
||||
if tradeHistory.Data[x].Gain == -1 {
|
||||
side = order.Sell
|
||||
}
|
||||
p := currency.NewPairFromString(strings.Replace(tradeHistory.Topic, "tradeHistory:", "", 1))
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(0, tradeHistory.Data[x].TransactionTime*int64(time.Millisecond)),
|
||||
CurrencyPair: p,
|
||||
AssetType: a,
|
||||
Exchange: b.Name,
|
||||
Price: tradeHistory.Data[x].Price,
|
||||
Amount: tradeHistory.Data[x].Amount,
|
||||
Side: side,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result["topic"].(string), "orderBookApi"):
|
||||
var t wsOrderBook
|
||||
err = json.Unmarshal(respRaw, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newOB orderbook.Base
|
||||
var price, amount float64
|
||||
for i := range t.Data.SellQuote {
|
||||
p := strings.Replace(t.Data.SellQuote[i].Price, ",", "", -1)
|
||||
price, err = strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a := strings.Replace(t.Data.SellQuote[i].Size, ",", "", -1)
|
||||
amount, err = strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newOB.Asks = append(newOB.Asks, orderbook.Item{
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
for j := range t.Data.BuyQuote {
|
||||
p := strings.Replace(t.Data.BuyQuote[j].Price, ",", "", -1)
|
||||
price, err = strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a := strings.Replace(t.Data.BuyQuote[j].Size, ",", "", -1)
|
||||
amount, err = strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newOB.Bids = append(newOB.Bids, orderbook.Item{
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
p := currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")])
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newOB.Pair = p
|
||||
newOB.AssetType = a
|
||||
newOB.ExchangeName = b.Name
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: a,
|
||||
Exchange: b.Name}
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *BTSE) GenerateDefaultSubscriptions() {
|
||||
var channels = []string{"orderBookApi:%s_0", "tradeHistory:%s"}
|
||||
pairs := b.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: "notificationApi",
|
||||
})
|
||||
}
|
||||
for i := range channels {
|
||||
for j := range pairs {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
@@ -179,6 +297,7 @@ func (b *BTSE) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscripti
|
||||
var sub wsSub
|
||||
sub.Operation = "subscribe"
|
||||
sub.Arguments = []string{channelToSubscribe.Channel}
|
||||
|
||||
return b.WebsocketConn.SendJSONMessage(sub)
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ func (b *BTSE) SetDefaults() {
|
||||
TradeFetching: true,
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.NoAPIWithdrawalMethods,
|
||||
},
|
||||
@@ -359,8 +361,8 @@ func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
|
||||
r, err := b.CreateOrder(s.Amount,
|
||||
s.Price,
|
||||
s.OrderSide.String(),
|
||||
s.OrderType.String(),
|
||||
s.Side.String(),
|
||||
s.Type.String(),
|
||||
b.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
goodTillCancel,
|
||||
s.ClientID)
|
||||
@@ -372,7 +374,7 @@ func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
resp.IsOrderPlaced = true
|
||||
resp.OrderID = *r
|
||||
}
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
resp.FullyMatched = true
|
||||
}
|
||||
return resp, nil
|
||||
@@ -386,8 +388,8 @@ 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 {
|
||||
r, err := b.CancelExistingOrder(order.OrderID,
|
||||
b.FormatExchangeCurrency(order.CurrencyPair,
|
||||
r, err := b.CancelExistingOrder(order.ID,
|
||||
b.FormatExchangeCurrency(order.Pair,
|
||||
asset.Spot).String())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -415,7 +417,7 @@ func (b *BTSE) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAll
|
||||
|
||||
resp.Status = make(map[string]string)
|
||||
for x := range markets {
|
||||
strPair := b.FormatExchangeCurrency(orderCancellation.CurrencyPair,
|
||||
strPair := b.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
orderCancellation.AssetType).String()
|
||||
checkPair := currency.NewPairWithDelimiter(markets[x].BaseCurrency,
|
||||
markets[x].QuoteCurrency,
|
||||
@@ -462,18 +464,18 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
side = order.Sell
|
||||
}
|
||||
|
||||
od.CurrencyPair = currency.NewPairDelimiter(o[i].Symbol,
|
||||
od.Pair = currency.NewPairDelimiter(o[i].Symbol,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
od.Exchange = b.Name
|
||||
od.Amount = o[i].Amount
|
||||
od.ID = o[i].ID
|
||||
od.OrderDate, err = parseOrderTime(o[i].CreatedAt)
|
||||
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.OrderSide = side
|
||||
od.OrderType = order.Type(strings.ToUpper(o[i].Type))
|
||||
od.Side = side
|
||||
od.Type = order.Type(strings.ToUpper(o[i].Type))
|
||||
od.Price = o[i].Price
|
||||
od.Status = order.Status(o[i].Status)
|
||||
|
||||
@@ -555,16 +557,16 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err
|
||||
}
|
||||
|
||||
openOrder := order.Detail{
|
||||
CurrencyPair: currency.NewPairDelimiter(resp[i].Symbol,
|
||||
Pair: currency.NewPairDelimiter(resp[i].Symbol,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
Exchange: b.Name,
|
||||
Amount: resp[i].Amount,
|
||||
ID: resp[i].ID,
|
||||
OrderDate: tm,
|
||||
OrderSide: side,
|
||||
OrderType: order.Type(strings.ToUpper(resp[i].Type)),
|
||||
Price: resp[i].Price,
|
||||
Status: order.Status(resp[i].Status),
|
||||
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, "", "", "", "", "")
|
||||
@@ -597,9 +599,9 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err
|
||||
orders = append(orders, openOrder)
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("CoinbasePro setup error", err)
|
||||
}
|
||||
|
||||
c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -426,8 +427,8 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.BTC,
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.BTC,
|
||||
currency.LTC)},
|
||||
}
|
||||
|
||||
@@ -441,8 +442,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.BTC,
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.BTC,
|
||||
currency.LTC)},
|
||||
}
|
||||
|
||||
@@ -471,11 +472,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := c.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -492,10 +493,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := c.CancelOrder(orderCancellation)
|
||||
@@ -514,10 +515,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := c.CancelAllOrders(orderCancellation)
|
||||
@@ -641,7 +642,7 @@ func TestWsAuth(t *testing.T) {
|
||||
}
|
||||
c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go c.WsHandleData()
|
||||
go c.wsReadData()
|
||||
err = c.Subscribe(wshandler.WebsocketChannelSubscription{
|
||||
Channel: "user",
|
||||
Currency: currency.NewPairFromString(testPair),
|
||||
@@ -657,3 +658,311 @@ func TestWsAuth(t *testing.T) {
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
func TestWsSubscribe(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "subscriptions",
|
||||
"channels": [
|
||||
{
|
||||
"name": "level2",
|
||||
"product_ids": [
|
||||
"ETH-USD",
|
||||
"ETH-EUR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "heartbeat",
|
||||
"product_ids": [
|
||||
"ETH-USD",
|
||||
"ETH-EUR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ticker",
|
||||
"product_ids": [
|
||||
"ETH-USD",
|
||||
"ETH-EUR",
|
||||
"ETH-BTC"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsHeartbeat(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "heartbeat",
|
||||
"sequence": 90,
|
||||
"last_trade_id": 20,
|
||||
"product_id": "BTC-USD",
|
||||
"time": "2014-11-07T08:19:28.464459Z"
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsStatus(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "status",
|
||||
"products": [
|
||||
{
|
||||
"id": "BTC-USD",
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"base_min_size": "0.001",
|
||||
"base_max_size": "70",
|
||||
"base_increment": "0.00000001",
|
||||
"quote_increment": "0.01",
|
||||
"display_name": "BTC/USD",
|
||||
"status": "online",
|
||||
"status_message": null,
|
||||
"min_market_funds": "10",
|
||||
"max_market_funds": "1000000",
|
||||
"post_only": false,
|
||||
"limit_only": false,
|
||||
"cancel_only": false
|
||||
}
|
||||
],
|
||||
"currencies": [
|
||||
{
|
||||
"id": "USD",
|
||||
"name": "United States Dollar",
|
||||
"min_size": "0.01000000",
|
||||
"status": "online",
|
||||
"status_message": null,
|
||||
"max_precision": "0.01",
|
||||
"convertible_to": ["USDC"], "details": {}
|
||||
},
|
||||
{
|
||||
"id": "USDC",
|
||||
"name": "USD Coin",
|
||||
"min_size": "0.00000100",
|
||||
"status": "online",
|
||||
"status_message": null,
|
||||
"max_precision": "0.000001",
|
||||
"convertible_to": ["USD"], "details": {}
|
||||
},
|
||||
{
|
||||
"id": "BTC",
|
||||
"name": "Bitcoin",
|
||||
"min_size": "0.00000001",
|
||||
"status": "online",
|
||||
"status_message": null,
|
||||
"max_precision": "0.00000001",
|
||||
"convertible_to": []
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "ticker",
|
||||
"trade_id": 20153558,
|
||||
"sequence": 3262786978,
|
||||
"time": "2017-09-02T17:05:49.250000Z",
|
||||
"product_id": "BTC-USD",
|
||||
"price": "4388.01000000",
|
||||
"side": "buy",
|
||||
"last_size": "0.03000000",
|
||||
"best_bid": "4388",
|
||||
"best_ask": "4388.01"
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderbook(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "snapshot",
|
||||
"product_id": "BTC-USD",
|
||||
"bids": [["10101.10", "0.45054140"]],
|
||||
"asks": [["10102.55", "0.57753524"]]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "l2update",
|
||||
"product_id": "BTC-USD",
|
||||
"time": "2019-08-14T20:42:27.265Z",
|
||||
"changes": [
|
||||
[
|
||||
"buy",
|
||||
"10101.80000000",
|
||||
"0.162567"
|
||||
]
|
||||
]
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrders(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "received",
|
||||
"time": "2014-11-07T08:19:27.028459Z",
|
||||
"product_id": "BTC-USD",
|
||||
"sequence": 10,
|
||||
"order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
|
||||
"size": "1.34",
|
||||
"price": "502.1",
|
||||
"side": "buy",
|
||||
"order_type": "limit"
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "received",
|
||||
"time": "2014-11-09T08:19:27.028459Z",
|
||||
"product_id": "BTC-USD",
|
||||
"sequence": 12,
|
||||
"order_id": "dddec984-77a8-460a-b958-66f114b0de9b",
|
||||
"funds": "3000.234",
|
||||
"side": "buy",
|
||||
"order_type": "market"
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "open",
|
||||
"time": "2014-11-07T08:19:27.028459Z",
|
||||
"product_id": "BTC-USD",
|
||||
"sequence": 10,
|
||||
"order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
|
||||
"price": "200.2",
|
||||
"remaining_size": "1.00",
|
||||
"side": "sell"
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "done",
|
||||
"time": "2014-11-07T08:19:27.028459Z",
|
||||
"product_id": "BTC-USD",
|
||||
"sequence": 10,
|
||||
"price": "200.2",
|
||||
"order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
|
||||
"reason": "filled",
|
||||
"side": "sell",
|
||||
"remaining_size": "0"
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "match",
|
||||
"trade_id": 10,
|
||||
"sequence": 50,
|
||||
"maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
|
||||
"taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1",
|
||||
"time": "2014-11-07T08:19:27.028459Z",
|
||||
"product_id": "BTC-USD",
|
||||
"size": "5.23512",
|
||||
"price": "400.23",
|
||||
"side": "sell"
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "change",
|
||||
"time": "2014-11-07T08:19:27.028459Z",
|
||||
"sequence": 80,
|
||||
"order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
|
||||
"product_id": "BTC-USD",
|
||||
"new_size": "5.23512",
|
||||
"old_size": "12.234412",
|
||||
"price": "400.23",
|
||||
"side": "sell"
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "change",
|
||||
"time": "2014-11-07T08:19:27.028459Z",
|
||||
"sequence": 80,
|
||||
"order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
|
||||
"product_id": "BTC-USD",
|
||||
"new_funds": "5.23512",
|
||||
"old_funds": "12.234412",
|
||||
"price": "400.23",
|
||||
"side": "sell"
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "activate",
|
||||
"product_id": "BTC-USD",
|
||||
"timestamp": "1483736448.299000",
|
||||
"user_id": "12",
|
||||
"profile_id": "30000727-d308-cf50-7b1c-c06deb1934fc",
|
||||
"order_id": "7b52009b-64fd-0a2a-49e6-d8a939753077",
|
||||
"stop_type": "entry",
|
||||
"side": "buy",
|
||||
"stop_price": "80",
|
||||
"size": "2",
|
||||
"funds": "50",
|
||||
"taker_fee_rate": "0.0025",
|
||||
"private": true
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusToStandardStatus(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Status
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "received", Result: order.New},
|
||||
{Case: "open", Result: order.Active},
|
||||
{Case: "done", Result: order.Filled},
|
||||
{Case: "match", Result: order.PartiallyFilled},
|
||||
{Case: "change", Result: order.Active},
|
||||
{Case: "activate", Result: order.Active},
|
||||
{Case: "LOL", Result: order.UnknownStatus},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := statusToStandardStatus(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,70 +365,34 @@ type WsChannels struct {
|
||||
ProductIDs []string `json:"product_ids"`
|
||||
}
|
||||
|
||||
// WebsocketReceived holds websocket received values
|
||||
type WebsocketReceived struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
OrderType string `json:"order_type"`
|
||||
Size float64 `json:"size,string"`
|
||||
Price float64 `json:"price,omitempty,string"`
|
||||
Funds float64 `json:"funds,omitempty,string"`
|
||||
Side string `json:"side"`
|
||||
ClientOID string `json:"client_oid"`
|
||||
ProductID string `json:"product_id"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// WebsocketOpen collates open orders
|
||||
type WebsocketOpen struct {
|
||||
Type string `json:"type"`
|
||||
Side string `json:"side"`
|
||||
Price float64 `json:"price,string"`
|
||||
OrderID string `json:"order_id"`
|
||||
RemainingSize float64 `json:"remaining_size,string"`
|
||||
ProductID string `json:"product_id"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// WebsocketDone holds finished order information
|
||||
type WebsocketDone struct {
|
||||
Type string `json:"type"`
|
||||
Side string `json:"side"`
|
||||
OrderID string `json:"order_id"`
|
||||
Reason string `json:"reason"`
|
||||
ProductID string `json:"product_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
RemainingSize float64 `json:"remaining_size,string"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// WebsocketMatch holds match information
|
||||
type WebsocketMatch struct {
|
||||
Type string `json:"type"`
|
||||
TradeID int `json:"trade_id"`
|
||||
MakerOrderID string `json:"maker_order_id"`
|
||||
TakerOrderID string `json:"taker_order_id"`
|
||||
Side string `json:"side"`
|
||||
Size float64 `json:"size,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
ProductID string `json:"product_id"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// WebsocketChange holds change information
|
||||
type WebsocketChange struct {
|
||||
Type string `json:"type"`
|
||||
Time string `json:"time"`
|
||||
Sequence int `json:"sequence"`
|
||||
OrderID string `json:"order_id"`
|
||||
NewSize float64 `json:"new_size,string"`
|
||||
OldSize float64 `json:"old_size,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
Side string `json:"side"`
|
||||
// wsOrderReceived holds websocket received values
|
||||
type wsOrderReceived struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
OrderType string `json:"order_type"`
|
||||
Size float64 `json:"size,string"`
|
||||
Price float64 `json:"price,omitempty,string"`
|
||||
Funds float64 `json:"funds,omitempty,string"`
|
||||
Side string `json:"side"`
|
||||
ClientOID string `json:"client_oid"`
|
||||
ProductID string `json:"product_id"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Time time.Time `json:"time"`
|
||||
RemainingSize float64 `json:"remaining_size,string"`
|
||||
NewSize float64 `json:"new_size,string"`
|
||||
OldSize float64 `json:"old_size,string"`
|
||||
Reason string `json:"reason"`
|
||||
Timestamp float64 `json:"timestamp,string"`
|
||||
UserID string `json:"user_id"`
|
||||
ProfileID string `json:"profile_id"`
|
||||
StopType string `json:"stop_type"`
|
||||
StopPrice float64 `json:"stop_price,string"`
|
||||
TakerFeeRate float64 `json:"taker_fee_rate,string"`
|
||||
Private bool `json:"private"`
|
||||
TradeID int64 `json:"trade_id"`
|
||||
MakerOrderID string `json:"maker_order_id"`
|
||||
TakerOrderID string `json:"taker_order_id"`
|
||||
TakerUserID string `json:"taker_user_id"`
|
||||
}
|
||||
|
||||
// WebsocketHeartBeat defines JSON response for a heart beat message
|
||||
@@ -475,19 +439,39 @@ type WebsocketL2Update struct {
|
||||
Changes [][]interface{} `json:"changes"`
|
||||
}
|
||||
|
||||
// WebsocketActivate an activate message is sent when a stop order is placed
|
||||
type WebsocketActivate struct {
|
||||
Type string `json:"type"`
|
||||
ProductID string `json:"product_id"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
UserID string `json:"user_id"`
|
||||
ProfileID string `json:"profile_id"`
|
||||
OrderID string `json:"order_id"`
|
||||
StopType string `json:"stop_type"`
|
||||
Side string `json:"side"`
|
||||
StopPrice float64 `json:"stop_price,string"`
|
||||
Size float64 `json:"size,string"`
|
||||
Funds float64 `json:"funds,string"`
|
||||
TakerFeeRate float64 `json:"taker_fee_rate,string"`
|
||||
Private bool `json:"private"`
|
||||
type wsMsgType struct {
|
||||
Type string `json:"type"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
ProductID string `json:"product_id"`
|
||||
}
|
||||
|
||||
type wsStatus struct {
|
||||
Currencies []struct {
|
||||
ConvertibleTo []string `json:"convertible_to"`
|
||||
Details struct{} `json:"details"`
|
||||
ID string `json:"id"`
|
||||
MaxPrecision float64 `json:"max_precision,string"`
|
||||
MinSize float64 `json:"min_size,string"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
StatusMessage interface{} `json:"status_message"`
|
||||
} `json:"currencies"`
|
||||
Products []struct {
|
||||
BaseCurrency string `json:"base_currency"`
|
||||
BaseIncrement float64 `json:"base_increment,string"`
|
||||
BaseMaxSize float64 `json:"base_max_size,string"`
|
||||
BaseMinSize float64 `json:"base_min_size,string"`
|
||||
CancelOnly bool `json:"cancel_only"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ID string `json:"id"`
|
||||
LimitOnly bool `json:"limit_only"`
|
||||
MaxMarketFunds float64 `json:"max_market_funds,string"`
|
||||
MinMarketFunds float64 `json:"min_market_funds,string"`
|
||||
PostOnly bool `json:"post_only"`
|
||||
QuoteCurrency string `json:"quote_currency"`
|
||||
QuoteIncrement float64 `json:"quote_increment,string"`
|
||||
Status string `json:"status"`
|
||||
StatusMessage interface{} `json:"status_message"`
|
||||
} `json:"products"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package coinbasepro
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
@@ -35,13 +37,13 @@ func (c *CoinbasePro) WsConnect() error {
|
||||
}
|
||||
|
||||
c.GenerateDefaultSubscriptions()
|
||||
go c.WsHandleData()
|
||||
go c.wsReadData()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles read data from websocket connection
|
||||
func (c *CoinbasePro) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (c *CoinbasePro) wsReadData() {
|
||||
c.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
@@ -59,127 +61,206 @@ func (c *CoinbasePro) WsHandleData() {
|
||||
return
|
||||
}
|
||||
c.Websocket.TrafficAlert <- struct{}{}
|
||||
|
||||
type MsgType struct {
|
||||
Type string `json:"type"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
ProductID string `json:"product_id"`
|
||||
}
|
||||
|
||||
msgType := MsgType{}
|
||||
err = json.Unmarshal(resp.Raw, &msgType)
|
||||
err = c.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
if msgType.Type == "subscriptions" || msgType.Type == "heartbeat" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch msgType.Type {
|
||||
case "error":
|
||||
c.Websocket.DataHandler <- errors.New(string(resp.Raw))
|
||||
|
||||
case "ticker":
|
||||
wsTicker := WebsocketTicker{}
|
||||
err := json.Unmarshal(resp.Raw, &wsTicker)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
c.Websocket.DataHandler <- &ticker.Price{
|
||||
LastUpdated: wsTicker.Time,
|
||||
Pair: wsTicker.ProductID,
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: c.Name,
|
||||
Open: wsTicker.Open24H,
|
||||
High: wsTicker.High24H,
|
||||
Low: wsTicker.Low24H,
|
||||
Last: wsTicker.Price,
|
||||
Volume: wsTicker.Volume24H,
|
||||
Bid: wsTicker.BestBid,
|
||||
Ask: wsTicker.BestAsk,
|
||||
}
|
||||
|
||||
case "snapshot":
|
||||
snapshot := WebsocketOrderbookSnapshot{}
|
||||
err := json.Unmarshal(resp.Raw, &snapshot)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.ProcessSnapshot(&snapshot)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
case "l2update":
|
||||
update := WebsocketL2Update{}
|
||||
err := json.Unmarshal(resp.Raw, &update)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.ProcessUpdate(update)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
case "received":
|
||||
// We currently use l2update to calculate orderbook changes
|
||||
received := WebsocketReceived{}
|
||||
err := json.Unmarshal(resp.Raw, &received)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- received
|
||||
case "open":
|
||||
// We currently use l2update to calculate orderbook changes
|
||||
open := WebsocketOpen{}
|
||||
err := json.Unmarshal(resp.Raw, &open)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- open
|
||||
case "done":
|
||||
// We currently use l2update to calculate orderbook changes
|
||||
done := WebsocketDone{}
|
||||
err := json.Unmarshal(resp.Raw, &done)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- done
|
||||
case "change":
|
||||
// We currently use l2update to calculate orderbook changes
|
||||
change := WebsocketChange{}
|
||||
err := json.Unmarshal(resp.Raw, &change)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- change
|
||||
case "activate":
|
||||
// We currently use l2update to calculate orderbook changes
|
||||
activate := WebsocketActivate{}
|
||||
err := json.Unmarshal(resp.Raw, &activate)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- activate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CoinbasePro) wsHandleData(respRaw []byte) error {
|
||||
msgType := wsMsgType{}
|
||||
err := json.Unmarshal(respRaw, &msgType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msgType.Type == "subscriptions" || msgType.Type == "heartbeat" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch msgType.Type {
|
||||
case "status":
|
||||
var status wsStatus
|
||||
err = json.Unmarshal(respRaw, &status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- status
|
||||
case "error":
|
||||
c.Websocket.DataHandler <- errors.New(string(respRaw))
|
||||
case "ticker":
|
||||
wsTicker := WebsocketTicker{}
|
||||
err := json.Unmarshal(respRaw, &wsTicker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Websocket.DataHandler <- &ticker.Price{
|
||||
LastUpdated: wsTicker.Time,
|
||||
Pair: wsTicker.ProductID,
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: c.Name,
|
||||
Open: wsTicker.Open24H,
|
||||
High: wsTicker.High24H,
|
||||
Low: wsTicker.Low24H,
|
||||
Last: wsTicker.Price,
|
||||
Volume: wsTicker.Volume24H,
|
||||
Bid: wsTicker.BestBid,
|
||||
Ask: wsTicker.BestAsk,
|
||||
}
|
||||
|
||||
case "snapshot":
|
||||
snapshot := WebsocketOrderbookSnapshot{}
|
||||
err := json.Unmarshal(respRaw, &snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.ProcessSnapshot(&snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "l2update":
|
||||
update := WebsocketL2Update{}
|
||||
err := json.Unmarshal(respRaw, &update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.ProcessUpdate(update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// the following cases contains data to synchronise authenticated orders
|
||||
// subscribing to the "full" channel will consider ALL cbp orders as
|
||||
// personal orders
|
||||
// remove sending &order.Detail to the datahandler if you wish to subscribe to the
|
||||
// "full" channel
|
||||
case "received", "open", "done", "change", "activate":
|
||||
var wsOrder wsOrderReceived
|
||||
err := json.Unmarshal(respRaw, &wsOrder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var oType order.Type
|
||||
var oSide order.Side
|
||||
var oStatus order.Status
|
||||
oType, err = order.StringToOrderType(wsOrder.OrderType)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: wsOrder.OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oSide, err = order.StringToOrderSide(wsOrder.Side)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: wsOrder.OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oStatus, err = statusToStandardStatus(wsOrder.Type)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: wsOrder.OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if wsOrder.Reason == "canceled" {
|
||||
oStatus = order.Cancelled
|
||||
}
|
||||
ts := wsOrder.Time
|
||||
if wsOrder.Type == "activate" {
|
||||
var one, two int64
|
||||
one, two, err = convert.SplitFloatDecimals(wsOrder.Timestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts = time.Unix(one, two)
|
||||
}
|
||||
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = c.GetRequestFormattedPairAndAssetType(wsOrder.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- &order.Detail{
|
||||
HiddenOrder: wsOrder.Private,
|
||||
Price: wsOrder.Price,
|
||||
Amount: wsOrder.Size,
|
||||
TriggerPrice: wsOrder.StopPrice,
|
||||
ExecutedAmount: wsOrder.Size - wsOrder.RemainingSize,
|
||||
RemainingAmount: wsOrder.RemainingSize,
|
||||
Fee: wsOrder.TakerFeeRate,
|
||||
Exchange: c.Name,
|
||||
ID: wsOrder.OrderID,
|
||||
AccountID: wsOrder.ProfileID,
|
||||
ClientID: c.API.Credentials.ClientID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: ts,
|
||||
Pair: p,
|
||||
}
|
||||
case "match":
|
||||
var wsOrder wsOrderReceived
|
||||
err := json.Unmarshal(respRaw, &wsOrder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oSide, err := order.StringToOrderSide(wsOrder.Side)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
c.Websocket.DataHandler <- &order.Detail{
|
||||
ID: wsOrder.OrderID,
|
||||
Trades: []order.TradeHistory{
|
||||
{
|
||||
Price: wsOrder.Price,
|
||||
Amount: wsOrder.Size,
|
||||
Exchange: c.Name,
|
||||
TID: strconv.FormatInt(wsOrder.TradeID, 10),
|
||||
Side: oSide,
|
||||
Timestamp: wsOrder.Time,
|
||||
IsMaker: wsOrder.TakerUserID == "",
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
c.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: c.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func statusToStandardStatus(stat string) (order.Status, error) {
|
||||
switch stat {
|
||||
case "received":
|
||||
return order.New, nil
|
||||
case "open":
|
||||
return order.Active, nil
|
||||
case "done":
|
||||
return order.Filled, nil
|
||||
case "match":
|
||||
return order.PartiallyFilled, nil
|
||||
case "change", "activate":
|
||||
return order.Active, nil
|
||||
default:
|
||||
return order.UnknownStatus, fmt.Errorf("%s not recognised as status type", stat)
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessSnapshot processes the initial orderbook snap shot
|
||||
func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) error {
|
||||
var base orderbook.Base
|
||||
|
||||
@@ -106,6 +106,8 @@ func (c *CoinbasePro) SetDefaults() {
|
||||
Unsubscribe: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
MessageSequenceNumbers: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.AutoWithdrawFiatWithAPIPermission,
|
||||
@@ -396,19 +398,19 @@ func (c *CoinbasePro) SubmitOrder(s *order.Submit) (order.SubmitResponse, error)
|
||||
|
||||
var response string
|
||||
var err error
|
||||
switch s.OrderType {
|
||||
switch s.Type {
|
||||
case order.Market:
|
||||
response, err = c.PlaceMarketOrder("",
|
||||
s.Amount,
|
||||
s.Amount,
|
||||
s.OrderSide.Lower(),
|
||||
s.Side.Lower(),
|
||||
c.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
"")
|
||||
case order.Limit:
|
||||
response, err = c.PlaceLimitOrder("",
|
||||
s.Price,
|
||||
s.Amount,
|
||||
s.OrderSide.Lower(),
|
||||
s.Side.Lower(),
|
||||
"",
|
||||
"",
|
||||
c.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
@@ -420,7 +422,7 @@ func (c *CoinbasePro) SubmitOrder(s *order.Submit) (order.SubmitResponse, error)
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
if response != "" {
|
||||
@@ -440,7 +442,7 @@ func (c *CoinbasePro) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (c *CoinbasePro) CancelOrder(order *order.Cancel) error {
|
||||
return c.CancelExistingOrder(order.OrderID)
|
||||
return c.CancelExistingOrder(order.ID)
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
@@ -532,9 +534,9 @@ func (c *CoinbasePro) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, er
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var respOrders []GeneralizedOrderResponse
|
||||
for i := range req.Currencies {
|
||||
for i := range req.Pairs {
|
||||
resp, err := c.GetOrders([]string{"open", "pending", "active"},
|
||||
c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String())
|
||||
c.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -561,17 +563,17 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta
|
||||
ID: respOrders[i].ID,
|
||||
Amount: respOrders[i].Size,
|
||||
ExecutedAmount: respOrders[i].FilledSize,
|
||||
OrderType: orderType,
|
||||
OrderDate: orderDate,
|
||||
OrderSide: orderSide,
|
||||
CurrencyPair: curr,
|
||||
Type: orderType,
|
||||
Date: orderDate,
|
||||
Side: orderSide,
|
||||
Pair: curr,
|
||||
Exchange: c.Name,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -579,9 +581,9 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta
|
||||
// Can Limit response to specific order status
|
||||
func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var respOrders []GeneralizedOrderResponse
|
||||
for i := range req.Currencies {
|
||||
for i := range req.Pairs {
|
||||
resp, err := c.GetOrders([]string{"done", "settled"},
|
||||
c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String())
|
||||
c.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -608,17 +610,17 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta
|
||||
ID: respOrders[i].ID,
|
||||
Amount: respOrders[i].Size,
|
||||
ExecutedAmount: respOrders[i].FilledSize,
|
||||
OrderType: orderType,
|
||||
OrderDate: orderDate,
|
||||
OrderSide: orderSide,
|
||||
CurrencyPair: curr,
|
||||
Type: orderType,
|
||||
Date: orderDate,
|
||||
Side: orderSide,
|
||||
Pair: curr,
|
||||
Exchange: c.Name,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
// Please supply your own keys here for due diligence testing
|
||||
@@ -42,7 +43,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -449,3 +451,233 @@ func TestGetSwapFundingRates(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSubscribe(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"event":"subscribe","topic":"orderBook.BTCUSDT.10"}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsUnsubscribe(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"event":"unsubscribe","topic":"tradeList.BTCUSDT"}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsLogin(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"event":"login","success":true}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{"event":"login","success":false}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderbook(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"topic": "orderBook.BTCUSDT",
|
||||
"action": "insert",
|
||||
"data": [{
|
||||
"asks": [
|
||||
["5621.7", "58", "2"],
|
||||
["5621.8", "125", "5"],
|
||||
["5621.9", "100", "9"],
|
||||
["5622", "84", "20"],
|
||||
["5623.5", "90", "12"],
|
||||
["5624.2", "1540", "15"],
|
||||
["5625.1", "300", "20"],
|
||||
["5625.9", "350", "1"],
|
||||
["5629.3", "200", "1"],
|
||||
["5650", "1000", "8"]
|
||||
],
|
||||
"bids": [
|
||||
["5621.3", "287","8"],
|
||||
["5621.2", "41","1"],
|
||||
["5621.1", "2","1"],
|
||||
["5621", "26","2"],
|
||||
["5620.8", "194","2"],
|
||||
["5620", "2", "1"],
|
||||
["5618.8", "204","2"],
|
||||
["5618.4", "30", "9"],
|
||||
["5617.2", "2","1"],
|
||||
["5609.9", "100", "12"]
|
||||
],
|
||||
"version":1,
|
||||
"timestamp": "2019-07-04T02:21:08Z"
|
||||
}]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"topic": "orderBook.BTCUSDT",
|
||||
"action": "update",
|
||||
"data": [{
|
||||
"asks": [
|
||||
["5621.7", "50", "2"],
|
||||
["5621.8", "0", "0"],
|
||||
["5621.9", "30", "5"]
|
||||
],
|
||||
"bids": [
|
||||
["5621.3", "10","1"],
|
||||
["5621.2", "20","1"],
|
||||
["5621.1", "80","5"],
|
||||
["5621", "0","0"],
|
||||
["5620.8", "10","1"]
|
||||
],
|
||||
"version":2,
|
||||
"timestamp": "2019-07-04T02:21:09Z"
|
||||
}]
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrade(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"topic": "tradeList.BTCUSDT",
|
||||
"data": [
|
||||
[
|
||||
"8600.0000",
|
||||
"s",
|
||||
"100",
|
||||
"2019-05-21T08:25:22.735Z"
|
||||
]
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"topic": "ticker.BTCUSDT",
|
||||
"data": [
|
||||
{
|
||||
"symbol": "BTCUSDT",
|
||||
"lastPrice": "8548.0",
|
||||
"markPrice": "8548.0",
|
||||
"bestAskPrice": "8601.0",
|
||||
"bestBidPrice": "8600.0",
|
||||
"bestAskVolume": "1222",
|
||||
"bestBidVolume": "56505",
|
||||
"high24h": "8600.0000",
|
||||
"low24h": "242.4500",
|
||||
"volume24h": "4994",
|
||||
"timestamp": "2019-05-06T06:45:56.716Z"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsKLine(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"topic": "kline.BTCUSDT",
|
||||
"data": [
|
||||
[
|
||||
"BTCUSDT",
|
||||
1557428280,
|
||||
"5794",
|
||||
"5794",
|
||||
"5794",
|
||||
"5794",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsUserAccount(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"topic": "user.account",
|
||||
"data": [{
|
||||
"asset": "BTC",
|
||||
"availableBalance": "20.3859",
|
||||
"frozenBalance": "0.7413",
|
||||
"balance": "21.1272",
|
||||
"timestamp": "2019-05-22T03:11:22.0Z"
|
||||
}]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsUserPosition(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"topic": "user.position",
|
||||
"data": [{
|
||||
"availableQuantity": "100",
|
||||
"avgPrice": "7778.1",
|
||||
"leverage": "20",
|
||||
"liquidationPrice": "5441.0",
|
||||
"markPrice": "8086.5",
|
||||
"positionMargin": "0.0285",
|
||||
"quantity": "507",
|
||||
"realisedPnl": "0.0069",
|
||||
"side": "long",
|
||||
"symbol": "BTCUSDT",
|
||||
"marginMode": "1",
|
||||
"createTime": "2019-05-22T03:11:22.0Z"
|
||||
}]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsUserOrder(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"topic": "user.order",
|
||||
"data": [{
|
||||
"orderId": "580721369818955776",
|
||||
"direction": "openLong",
|
||||
"leverage": "20",
|
||||
"symbol": "BTCUSDT",
|
||||
"orderType": "limit",
|
||||
"quantity": "7",
|
||||
"orderPrice": "146.30",
|
||||
"orderValue": "0.0010",
|
||||
"fee": "0.0000",
|
||||
"filledQuantity": "0",
|
||||
"averagePrice": "0.00",
|
||||
"orderTime": "2019-05-22T03:39:24.0Z",
|
||||
"status": "new",
|
||||
"lastFillQuantity": "0",
|
||||
"lastFillPrice": "0",
|
||||
"lastFillTime": ""
|
||||
}]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,20 +85,20 @@ type CancelOrdersResponse struct {
|
||||
|
||||
// OrderInfo stores order info
|
||||
type OrderInfo struct {
|
||||
OrderID string `json:"orderId"`
|
||||
BaseAsset string `json:"baseAsset"`
|
||||
QuoteAsset string `json:"quoteAsset"`
|
||||
OrderType string `json:"orderDirection"`
|
||||
Quantity float64 `json:"quntity,string"`
|
||||
Amount float64 `json:"amout,string"`
|
||||
FilledAmount float64 `json:"filledAmount"`
|
||||
TakerRate float64 `json:"takerFeeRate,string"`
|
||||
MakerRate float64 `json:"makerFeeRate,string"`
|
||||
AvgPrice float64 `json:"avgPrice,string"`
|
||||
OrderPrice float64 `json:"orderPrice,string"`
|
||||
OrderStatus string `json:"orderStatus"`
|
||||
OrderTime string `json:"orderTime"`
|
||||
TotalFee float64 `json:"totalFee"`
|
||||
OrderID string `json:"orderId"`
|
||||
BaseAsset string `json:"baseAsset"`
|
||||
QuoteAsset string `json:"quoteAsset"`
|
||||
OrderType string `json:"orderDirection"`
|
||||
Quantity float64 `json:"quntity,string"`
|
||||
Amount float64 `json:"amout,string"`
|
||||
FilledAmount float64 `json:"filledAmount"`
|
||||
TakerRate float64 `json:"takerFeeRate,string"`
|
||||
MakerRate float64 `json:"makerFeeRate,string"`
|
||||
AvgPrice float64 `json:"avgPrice,string"`
|
||||
OrderPrice float64 `json:"orderPrice,string"`
|
||||
OrderStatus string `json:"orderStatus"`
|
||||
OrderTime time.Time `json:"orderTime"`
|
||||
TotalFee float64 `json:"totalFee"`
|
||||
}
|
||||
|
||||
// OrderFills stores the fill info
|
||||
@@ -157,9 +157,9 @@ type WsKline struct {
|
||||
// WsUserData stores websocket user data
|
||||
type WsUserData struct {
|
||||
Asset string `json:"string"`
|
||||
Available float64 `json:"availableBalance"`
|
||||
Locked float64 `json:"frozenBalance"`
|
||||
Total float64 `json:"balance"`
|
||||
Available float64 `json:"availableBalance,string"`
|
||||
Locked float64 `json:"frozenBalance,string"`
|
||||
Total float64 `json:"balance,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
@@ -171,17 +171,17 @@ type WsUserInfo struct {
|
||||
|
||||
// WsPositionData stores websocket info on user's position
|
||||
type WsPositionData struct {
|
||||
AvailableQuantity float64 `json:"availableQuantity"`
|
||||
AveragePrice float64 `json:"avgPrice"`
|
||||
Leverage float64 `json:"leverage"`
|
||||
LiquidationPrice float64 `json:"liquidationPrice"`
|
||||
MarkPrice float64 `json:"markPrice"`
|
||||
PositionMargin float64 `json:"positionMargin"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
RealisedPNL float64 `json:"realisedPnl"`
|
||||
AvailableQuantity float64 `json:"availableQuantity,string"`
|
||||
AveragePrice float64 `json:"avgPrice,string"`
|
||||
Leverage int64 `json:"leverage,string"`
|
||||
LiquidationPrice float64 `json:"liquidationPrice,string"`
|
||||
MarkPrice float64 `json:"markPrice,string"`
|
||||
PositionMargin float64 `json:"positionMargin,string"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
RealisedPNL float64 `json:"realisedPnl,string"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
MarginMode int64 `json:"marginMode"`
|
||||
MarginMode int64 `json:"marginMode,string"`
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
}
|
||||
|
||||
@@ -195,20 +195,20 @@ type WsPosition struct {
|
||||
type WsOrderData struct {
|
||||
OrderID string `json:"orderId"`
|
||||
Direction string `json:"direction"`
|
||||
Leverage float64 `json:"leverage"`
|
||||
Leverage int64 `json:"leverage,string"`
|
||||
Symbol string `json:"symbol"`
|
||||
OrderType string `json:"orderType"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
OrderPrice float64 `json:"orderPrice"`
|
||||
OrderValue float64 `json:"orderValue"`
|
||||
Fee float64 `json:"fee"`
|
||||
FilledQuantity float64 `json:"filledQuantity"`
|
||||
AveragePrice float64 `json:"averagePrice"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
OrderPrice float64 `json:"orderPrice,string"`
|
||||
OrderValue float64 `json:"orderValue,string"`
|
||||
Fee float64 `json:"fee,string"`
|
||||
FilledQuantity float64 `json:"filledQuantity,string"`
|
||||
AveragePrice float64 `json:"averagePrice,string"`
|
||||
OrderTime time.Time `json:"orderTime"`
|
||||
Status string `json:"status"`
|
||||
LastFillQuantity float64 `json:"lastFillQuantity"`
|
||||
LastFillPrice float64 `json:"lastFillPrice"`
|
||||
LastFillTime time.Time `json:"lastFillTime"`
|
||||
LastFillQuantity float64 `json:"lastFillQuantity,string"`
|
||||
LastFillPrice float64 `json:"lastFillPrice,string"`
|
||||
LastFillTime string `json:"lastFillTime"`
|
||||
}
|
||||
|
||||
// WsUserOrders stores websocket user orders' data
|
||||
@@ -264,12 +264,12 @@ type SwapTrades []SwapTrade
|
||||
|
||||
// SwapAccountInfo returns the swap account balance info
|
||||
type SwapAccountInfo struct {
|
||||
AvailableBalance float64 `json:"availableBalance,string"`
|
||||
FrozenBalance float64 `json:"frozenBalance,string"`
|
||||
MarginBalance float64 `json:"marginBalance,string"`
|
||||
MarginRate float64 `json:"marginRate,string"`
|
||||
Balance float64 `json:"balance,string"`
|
||||
UnrealisedPNL float64 `json:"unrealisedPnl,string"`
|
||||
AvailableBalance float64 `json:"availableBalance,string"`
|
||||
FrozenBalance float64 `json:"frozenBalance,string"`
|
||||
MarginBalance float64 `json:"marginBalance,string"`
|
||||
MarginRate float64 `json:"marginRate,string"`
|
||||
Balance float64 `json:"balance,string"`
|
||||
UnrealisedProfitAndLoss float64 `json:"unrealisedPnl,string"`
|
||||
}
|
||||
|
||||
// SwapPosition stores a single swap position's data
|
||||
@@ -277,8 +277,8 @@ type SwapPosition struct {
|
||||
AvailableQuantity float64 `json:"availableQuantity,string"`
|
||||
AveragePrice float64 `json:"averagePrice,string"`
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
DeleveragePercentile int `json:"deleveragePercentile,string"`
|
||||
Leverage int `json:"leverage,string"`
|
||||
DeleveragePercentile int64 `json:"deleveragePercentile,string"`
|
||||
Leverage int64 `json:"leverage,string"`
|
||||
LiquidationPrice float64 `json:"liquidationPrice,string"`
|
||||
MarkPrice float64 `json:"markPrice,string"`
|
||||
PositionMargin float64 `json:"positionMargin,string"`
|
||||
@@ -303,9 +303,9 @@ type SwapPlaceOrderResponse struct {
|
||||
type SwapOrder struct {
|
||||
OrderID string `json:"orderId"`
|
||||
Direction string `json:"direction"`
|
||||
Leverage int `json:"leverage,string"`
|
||||
Leverage int64 `json:"leverage,string"`
|
||||
OrderType string `json:"orderType"`
|
||||
Quantitity float64 `json:"quantity,string"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
OrderPrice float64 `json:"orderPrice,string"`
|
||||
OrderValue float64 `json:"orderValue,string"`
|
||||
Fee float64 `json:"fee"`
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -21,11 +22,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
coinbeneWsURL = "wss://ws-contract.coinbene.vip/openapi/ws"
|
||||
wsContractURL = "wss://ws-contract.coinbene.vip/openapi/ws"
|
||||
event = "event"
|
||||
topic = "topic"
|
||||
)
|
||||
|
||||
var comms = make(chan wshandler.WebsocketResponse)
|
||||
|
||||
// WsConnect connects to websocket
|
||||
func (c *Coinbene) WsConnect() error {
|
||||
if !c.Websocket.IsEnabled() || !c.IsEnabled() {
|
||||
@@ -36,7 +39,8 @@ func (c *Coinbene) WsConnect() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go c.WsDataHandler()
|
||||
|
||||
go c.wsReadData()
|
||||
if c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
err = c.Login()
|
||||
if err != nil {
|
||||
@@ -78,266 +82,294 @@ func (c *Coinbene) GenerateAuthSubs() {
|
||||
c.Websocket.SubscribeToChannels(subscriptions)
|
||||
}
|
||||
|
||||
// WsDataHandler handles websocket data
|
||||
func (c *Coinbene) WsDataHandler() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (c *Coinbene) wsReadData() {
|
||||
c.Websocket.Wg.Add(1)
|
||||
|
||||
defer c.Websocket.Wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
default:
|
||||
stream, err := c.WebsocketConn.ReadMessage()
|
||||
resp, err := c.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
c.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
c.Websocket.TrafficAlert <- struct{}{}
|
||||
if string(stream.Raw) == wshandler.Ping {
|
||||
err = c.WebsocketConn.SendRawMessage(websocket.TextMessage, []byte(wshandler.Pong))
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
}
|
||||
continue
|
||||
}
|
||||
var result map[string]interface{}
|
||||
err = json.Unmarshal(stream.Raw, &result)
|
||||
err = c.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
}
|
||||
_, ok := result[event]
|
||||
switch {
|
||||
case ok && (result[event].(string) == "subscribe" || result[event].(string) == "unsubscribe"):
|
||||
continue
|
||||
case ok && result[event].(string) == "error":
|
||||
c.Websocket.DataHandler <- fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
|
||||
continue
|
||||
}
|
||||
if ok && strings.Contains(result[event].(string), "login") {
|
||||
if result["success"].(bool) {
|
||||
c.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
c.GenerateAuthSubs()
|
||||
continue
|
||||
}
|
||||
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
c.Websocket.DataHandler <- fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(result[topic].(string), "ticker"):
|
||||
var wsTicker WsTicker
|
||||
err = json.Unmarshal(stream.Raw, &wsTicker)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
for x := range wsTicker.Data {
|
||||
c.Websocket.DataHandler <- &ticker.Price{
|
||||
Volume: wsTicker.Data[x].Volume24h,
|
||||
Last: wsTicker.Data[x].LastPrice,
|
||||
High: wsTicker.Data[x].High24h,
|
||||
Low: wsTicker.Data[x].Low24h,
|
||||
Bid: wsTicker.Data[x].BestBidPrice,
|
||||
Ask: wsTicker.Data[x].BestAskPrice,
|
||||
Pair: currency.NewPairFromFormattedPairs(wsTicker.Data[x].Symbol,
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true)),
|
||||
ExchangeName: c.Name,
|
||||
AssetType: asset.PerpetualSwap,
|
||||
LastUpdated: wsTicker.Data[x].Timestamp,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "tradeList"):
|
||||
var tradeList WsTradeList
|
||||
err = json.Unmarshal(stream.Raw, &tradeList)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
var t time.Time
|
||||
var price, amount float64
|
||||
t, err = time.Parse(time.RFC3339, tradeList.Data[0][3])
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
price, err = strconv.ParseFloat(tradeList.Data[0][0], 64)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
amount, err = strconv.ParseFloat(tradeList.Data[0][2], 64)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
p := strings.Replace(tradeList.Topic, "tradeList.", "", 1)
|
||||
c.Websocket.DataHandler <- wshandler.TradeData{
|
||||
CurrencyPair: currency.NewPairFromFormattedPairs(p,
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true)),
|
||||
Timestamp: t,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: c.Name,
|
||||
AssetType: asset.PerpetualSwap,
|
||||
Side: tradeList.Data[0][1],
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "orderBook"):
|
||||
orderBook := struct {
|
||||
Topic string `json:"topic"`
|
||||
Action string `json:"action"`
|
||||
Data []struct {
|
||||
Bids [][]string `json:"bids"`
|
||||
Asks [][]string `json:"asks"`
|
||||
Version int64 `json:"version"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
} `json:"data"`
|
||||
}{}
|
||||
err = json.Unmarshal(stream.Raw, &orderBook)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
p := strings.Replace(orderBook.Topic, "orderBook.", "", 1)
|
||||
cp := currency.NewPairFromFormattedPairs(p,
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true))
|
||||
var amount, price float64
|
||||
var asks, bids []orderbook.Item
|
||||
for i := range orderBook.Data[0].Asks {
|
||||
amount, err = strconv.ParseFloat(orderBook.Data[0].Asks[i][1], 64)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
price, err = strconv.ParseFloat(orderBook.Data[0].Asks[i][0], 64)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
for j := range orderBook.Data[0].Bids {
|
||||
amount, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][1], 64)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
price, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][0], 64)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
if orderBook.Action == "insert" {
|
||||
var newOB orderbook.Base
|
||||
newOB.Asks = asks
|
||||
newOB.Bids = bids
|
||||
newOB.AssetType = asset.PerpetualSwap
|
||||
newOB.Pair = cp
|
||||
newOB.ExchangeName = c.Name
|
||||
newOB.LastUpdated = orderBook.Data[0].Timestamp
|
||||
err = c.Websocket.Orderbook.LoadSnapshot(&newOB)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: asset.PerpetualSwap,
|
||||
Exchange: c.Name,
|
||||
}
|
||||
} else if orderBook.Action == "update" {
|
||||
newOB := wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Asset: asset.PerpetualSwap,
|
||||
Pair: cp,
|
||||
UpdateID: orderBook.Data[0].Version,
|
||||
UpdateTime: orderBook.Data[0].Timestamp,
|
||||
}
|
||||
err = c.Websocket.Orderbook.Update(&newOB)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: asset.PerpetualSwap,
|
||||
Exchange: c.Name,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "kline"):
|
||||
var kline WsKline
|
||||
var tempFloat float64
|
||||
var tempKline []float64
|
||||
err = json.Unmarshal(stream.Raw, &kline)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
for x := 2; x < len(kline.Data[0]); x++ {
|
||||
tempFloat, err = strconv.ParseFloat(kline.Data[0][x].(string), 64)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
tempKline = append(tempKline, tempFloat)
|
||||
}
|
||||
p := currency.NewPairFromFormattedPairs(kline.Data[0][0].(string),
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true))
|
||||
c.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(int64(kline.Data[0][1].(float64)), 0),
|
||||
Pair: p,
|
||||
AssetType: asset.PerpetualSwap,
|
||||
Exchange: c.Name,
|
||||
OpenPrice: tempKline[0],
|
||||
ClosePrice: tempKline[1],
|
||||
HighPrice: tempKline[2],
|
||||
LowPrice: tempKline[3],
|
||||
Volume: tempKline[4],
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "user.account"):
|
||||
var userinfo WsUserInfo
|
||||
err = json.Unmarshal(stream.Raw, &userinfo)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- userinfo
|
||||
case strings.Contains(result[topic].(string), "user.position"):
|
||||
var position WsPosition
|
||||
err = json.Unmarshal(stream.Raw, &position)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- position
|
||||
case strings.Contains(result[topic].(string), "user.order"):
|
||||
var orders WsUserOrders
|
||||
err = json.Unmarshal(stream.Raw, &orders)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.Websocket.DataHandler <- orders
|
||||
default:
|
||||
c.Websocket.DataHandler <- fmt.Errorf("%s - unhandled response '%s'", c.Name, stream.Raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Coinbene) wsHandleData(respRaw []byte) error {
|
||||
c.Websocket.TrafficAlert <- struct{}{}
|
||||
if string(respRaw) == wshandler.Ping {
|
||||
err := c.WebsocketConn.SendRawMessage(websocket.TextMessage, []byte(wshandler.Pong))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := result[event]
|
||||
switch {
|
||||
case ok && (result[event].(string) == "subscribe" || result[event].(string) == "unsubscribe"):
|
||||
return nil
|
||||
case ok && result[event].(string) == "error":
|
||||
return fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
|
||||
}
|
||||
if ok && strings.Contains(result[event].(string), "login") {
|
||||
if result["success"].(bool) {
|
||||
c.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
c.GenerateAuthSubs()
|
||||
return nil
|
||||
}
|
||||
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
return fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(result[topic].(string), "ticker"):
|
||||
var wsTicker WsTicker
|
||||
err = json.Unmarshal(respRaw, &wsTicker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for x := range wsTicker.Data {
|
||||
c.Websocket.DataHandler <- &ticker.Price{
|
||||
Volume: wsTicker.Data[x].Volume24h,
|
||||
Last: wsTicker.Data[x].LastPrice,
|
||||
High: wsTicker.Data[x].High24h,
|
||||
Low: wsTicker.Data[x].Low24h,
|
||||
Bid: wsTicker.Data[x].BestBidPrice,
|
||||
Ask: wsTicker.Data[x].BestAskPrice,
|
||||
Pair: currency.NewPairFromFormattedPairs(wsTicker.Data[x].Symbol,
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true)),
|
||||
ExchangeName: c.Name,
|
||||
AssetType: asset.PerpetualSwap,
|
||||
LastUpdated: wsTicker.Data[x].Timestamp,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "tradeList"):
|
||||
var tradeList WsTradeList
|
||||
err = json.Unmarshal(respRaw, &tradeList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var t time.Time
|
||||
var price, amount float64
|
||||
t, err = time.Parse(time.RFC3339, tradeList.Data[0][3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
price, err = strconv.ParseFloat(tradeList.Data[0][0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount, err = strconv.ParseFloat(tradeList.Data[0][2], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := strings.Replace(tradeList.Topic, "tradeList.", "", 1)
|
||||
var tSide = order.Buy
|
||||
if tradeList.Data[0][1] == "s" {
|
||||
tSide = order.Sell
|
||||
}
|
||||
c.Websocket.DataHandler <- wshandler.TradeData{
|
||||
CurrencyPair: currency.NewPairFromFormattedPairs(p,
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true)),
|
||||
Timestamp: t,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: c.Name,
|
||||
AssetType: asset.PerpetualSwap,
|
||||
Side: tSide,
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "orderBook"):
|
||||
orderBook := struct {
|
||||
Topic string `json:"topic"`
|
||||
Action string `json:"action"`
|
||||
Data []struct {
|
||||
Bids [][]string `json:"bids"`
|
||||
Asks [][]string `json:"asks"`
|
||||
Version int64 `json:"version"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
} `json:"data"`
|
||||
}{}
|
||||
err = json.Unmarshal(respRaw, &orderBook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := strings.Replace(orderBook.Topic, "orderBook.", "", 1)
|
||||
cp := currency.NewPairFromFormattedPairs(p,
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true))
|
||||
var amount, price float64
|
||||
var asks, bids []orderbook.Item
|
||||
for i := range orderBook.Data[0].Asks {
|
||||
amount, err = strconv.ParseFloat(orderBook.Data[0].Asks[i][1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
price, err = strconv.ParseFloat(orderBook.Data[0].Asks[i][0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
for j := range orderBook.Data[0].Bids {
|
||||
amount, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
price, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
if orderBook.Action == "insert" {
|
||||
var newOB orderbook.Base
|
||||
newOB.Asks = asks
|
||||
newOB.Bids = bids
|
||||
newOB.AssetType = asset.PerpetualSwap
|
||||
newOB.Pair = cp
|
||||
newOB.ExchangeName = c.Name
|
||||
newOB.LastUpdated = orderBook.Data[0].Timestamp
|
||||
err = c.Websocket.Orderbook.LoadSnapshot(&newOB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: asset.PerpetualSwap,
|
||||
Exchange: c.Name,
|
||||
}
|
||||
} else if orderBook.Action == "update" {
|
||||
newOB := wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Asset: asset.PerpetualSwap,
|
||||
Pair: cp,
|
||||
UpdateID: orderBook.Data[0].Version,
|
||||
UpdateTime: orderBook.Data[0].Timestamp,
|
||||
}
|
||||
err = c.Websocket.Orderbook.Update(&newOB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: asset.PerpetualSwap,
|
||||
Exchange: c.Name,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "kline"):
|
||||
var kline WsKline
|
||||
var tempFloat float64
|
||||
var tempKline []float64
|
||||
err = json.Unmarshal(respRaw, &kline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for x := 2; x < len(kline.Data[0]); x++ {
|
||||
tempFloat, err = strconv.ParseFloat(kline.Data[0][x].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempKline = append(tempKline, tempFloat)
|
||||
}
|
||||
p := currency.NewPairFromFormattedPairs(kline.Data[0][0].(string),
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true))
|
||||
if tempKline == nil && len(tempKline) < 5 {
|
||||
return errors.New(c.Name + " - received bad data ")
|
||||
}
|
||||
c.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(int64(kline.Data[0][1].(float64)), 0),
|
||||
Pair: p,
|
||||
AssetType: asset.PerpetualSwap,
|
||||
Exchange: c.Name,
|
||||
OpenPrice: tempKline[0],
|
||||
ClosePrice: tempKline[1],
|
||||
HighPrice: tempKline[2],
|
||||
LowPrice: tempKline[3],
|
||||
Volume: tempKline[4],
|
||||
}
|
||||
case strings.Contains(result[topic].(string), "user.account"):
|
||||
var userInfo WsUserInfo
|
||||
err = json.Unmarshal(respRaw, &userInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- userInfo
|
||||
case strings.Contains(result[topic].(string), "user.position"):
|
||||
var position WsPosition
|
||||
err = json.Unmarshal(respRaw, &position)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- position
|
||||
case strings.Contains(result[topic].(string), "user.order"):
|
||||
var orders WsUserOrders
|
||||
err = json.Unmarshal(respRaw, &orders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range orders.Data {
|
||||
oType, err := order.StringToOrderType(orders.Data[i].OrderType)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: orders.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oStatus, err := order.StringToOrderStatus(orders.Data[i].Status)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: orders.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
c.Websocket.DataHandler <- &order.Detail{
|
||||
Price: orders.Data[i].OrderPrice,
|
||||
Amount: orders.Data[i].Quantity,
|
||||
ExecutedAmount: orders.Data[i].FilledQuantity,
|
||||
RemainingAmount: orders.Data[i].Quantity - orders.Data[i].FilledQuantity,
|
||||
Fee: orders.Data[i].Fee,
|
||||
Exchange: c.Name,
|
||||
ID: orders.Data[i].OrderID,
|
||||
Type: oType,
|
||||
Status: oStatus,
|
||||
AssetType: asset.PerpetualSwap,
|
||||
Date: orders.Data[i].OrderTime,
|
||||
Leverage: strconv.FormatInt(orders.Data[i].Leverage, 10),
|
||||
Pair: currency.NewPairFromFormattedPairs(orders.Data[i].Symbol,
|
||||
c.GetEnabledPairs(asset.PerpetualSwap),
|
||||
c.GetPairFormat(asset.PerpetualSwap, true)),
|
||||
}
|
||||
}
|
||||
default:
|
||||
c.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: c.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (c *Coinbene) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
var sub WsSub
|
||||
|
||||
@@ -108,6 +108,8 @@ func (c *Coinbene) SetDefaults() {
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.NoFiatWithdrawals |
|
||||
exchange.WithdrawCryptoViaWebsiteOnly,
|
||||
@@ -122,7 +124,7 @@ func (c *Coinbene) SetDefaults() {
|
||||
|
||||
c.API.Endpoints.URLDefault = coinbeneAPIURL
|
||||
c.API.Endpoints.URL = c.API.Endpoints.URLDefault
|
||||
c.API.Endpoints.WebsocketURL = coinbeneWsURL
|
||||
c.API.Endpoints.WebsocketURL = wsContractURL
|
||||
c.Websocket = wshandler.New()
|
||||
c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
@@ -147,7 +149,7 @@ func (c *Coinbene) Setup(exch *config.ExchangeConfig) error {
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: coinbeneWsURL,
|
||||
DefaultURL: wsContractURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: c.WsConnect,
|
||||
@@ -470,20 +472,20 @@ func (c *Coinbene) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if s.OrderSide != order.Buy && s.OrderSide != order.Sell {
|
||||
if s.Side != order.Buy && s.Side != order.Sell {
|
||||
return resp,
|
||||
fmt.Errorf("%s orderside is not supported by this exchange",
|
||||
s.OrderSide)
|
||||
s.Side)
|
||||
}
|
||||
if s.OrderType != order.Limit {
|
||||
if s.Type != order.Limit {
|
||||
return resp, fmt.Errorf("only limit order is supported by this exchange")
|
||||
}
|
||||
|
||||
tempResp, err := c.PlaceSpotOrder(s.Price,
|
||||
s.Amount,
|
||||
c.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
s.OrderSide.String(),
|
||||
s.OrderType.String(),
|
||||
s.Side.String(),
|
||||
s.Type.String(),
|
||||
s.ClientID,
|
||||
0)
|
||||
if err != nil {
|
||||
@@ -502,7 +504,7 @@ func (c *Coinbene) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (c *Coinbene) CancelOrder(order *order.Cancel) error {
|
||||
_, err := c.CancelSpotOrder(order.OrderID)
|
||||
_, err := c.CancelSpotOrder(order.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -510,7 +512,7 @@ func (c *Coinbene) CancelOrder(order *order.Cancel) error {
|
||||
func (c *Coinbene) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
|
||||
var resp order.CancelAllResponse
|
||||
orders, err := c.FetchOpenSpotOrders(
|
||||
c.FormatExchangeCurrency(orderCancellation.CurrencyPair,
|
||||
c.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
asset.Spot).String(),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -536,18 +538,13 @@ func (c *Coinbene) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
var t time.Time
|
||||
resp.Exchange = c.Name
|
||||
resp.ID = orderID
|
||||
resp.CurrencyPair = currency.NewPairWithDelimiter(tempResp.BaseAsset,
|
||||
resp.Pair = currency.NewPairWithDelimiter(tempResp.BaseAsset,
|
||||
"/",
|
||||
tempResp.QuoteAsset)
|
||||
t, err = time.Parse(time.RFC3339, tempResp.OrderTime)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp.Price = tempResp.OrderPrice
|
||||
resp.OrderDate = t
|
||||
resp.Date = tempResp.OrderTime
|
||||
resp.ExecutedAmount = tempResp.FilledAmount
|
||||
resp.Fee = tempResp.TotalFee
|
||||
return resp, nil
|
||||
@@ -583,13 +580,13 @@ func (c *Coinbene) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(getOrdersRequest.Currencies) == 0 {
|
||||
if len(getOrdersRequest.Pairs) == 0 {
|
||||
allPairs, err := c.GetAllPairs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for a := range allPairs {
|
||||
getOrdersRequest.Currencies = append(getOrdersRequest.Currencies,
|
||||
getOrdersRequest.Pairs = append(getOrdersRequest.Pairs,
|
||||
currency.NewPairFromString(allPairs[a].Symbol))
|
||||
}
|
||||
}
|
||||
@@ -597,11 +594,11 @@ func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]
|
||||
var err error
|
||||
var resp []order.Detail
|
||||
|
||||
for x := range getOrdersRequest.Currencies {
|
||||
for x := range getOrdersRequest.Pairs {
|
||||
var tempData OrdersInfo
|
||||
tempData, err = c.FetchOpenSpotOrders(
|
||||
c.FormatExchangeCurrency(
|
||||
getOrdersRequest.Currencies[x],
|
||||
getOrdersRequest.Pairs[x],
|
||||
asset.Spot).String(),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -611,19 +608,12 @@ func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]
|
||||
for y := range tempData {
|
||||
var tempResp order.Detail
|
||||
tempResp.Exchange = c.Name
|
||||
tempResp.CurrencyPair = getOrdersRequest.Currencies[x]
|
||||
tempResp.OrderSide = order.Buy
|
||||
tempResp.Pair = getOrdersRequest.Pairs[x]
|
||||
tempResp.Side = order.Buy
|
||||
if strings.EqualFold(tempData[y].OrderType, order.Sell.String()) {
|
||||
tempResp.OrderSide = order.Sell
|
||||
tempResp.Side = order.Sell
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
t, err = time.Parse(time.RFC3339, tempData[y].OrderTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempResp.OrderDate = t
|
||||
tempResp.Date = tempData[y].OrderTime
|
||||
tempResp.Status = order.Status(tempData[y].OrderStatus)
|
||||
tempResp.Price = tempData[y].OrderPrice
|
||||
tempResp.Amount = tempData[y].Amount
|
||||
@@ -639,13 +629,13 @@ func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (c *Coinbene) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(getOrdersRequest.Currencies) == 0 {
|
||||
if len(getOrdersRequest.Pairs) == 0 {
|
||||
allPairs, err := c.GetAllPairs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for a := range allPairs {
|
||||
getOrdersRequest.Currencies = append(getOrdersRequest.Currencies,
|
||||
getOrdersRequest.Pairs = append(getOrdersRequest.Pairs,
|
||||
currency.NewPairFromString(allPairs[a].Symbol))
|
||||
}
|
||||
}
|
||||
@@ -654,10 +644,10 @@ func (c *Coinbene) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]
|
||||
var tempData OrdersInfo
|
||||
var err error
|
||||
|
||||
for x := range getOrdersRequest.Currencies {
|
||||
for x := range getOrdersRequest.Pairs {
|
||||
tempData, err = c.FetchClosedOrders(
|
||||
c.FormatExchangeCurrency(
|
||||
getOrdersRequest.Currencies[x],
|
||||
getOrdersRequest.Pairs[x],
|
||||
asset.Spot).String(),
|
||||
"",
|
||||
)
|
||||
@@ -668,19 +658,12 @@ func (c *Coinbene) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]
|
||||
for y := range tempData {
|
||||
var tempResp order.Detail
|
||||
tempResp.Exchange = c.Name
|
||||
tempResp.CurrencyPair = getOrdersRequest.Currencies[x]
|
||||
tempResp.OrderSide = order.Buy
|
||||
tempResp.Pair = getOrdersRequest.Pairs[x]
|
||||
tempResp.Side = order.Buy
|
||||
if strings.EqualFold(tempData[y].OrderType, order.Sell.String()) {
|
||||
tempResp.OrderSide = order.Sell
|
||||
tempResp.Side = order.Sell
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
t, err = time.Parse(time.RFC3339, tempData[y].OrderTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempResp.OrderDate = t
|
||||
tempResp.Date = tempData[y].OrderTime
|
||||
tempResp.Status = order.Status(tempData[y].OrderStatus)
|
||||
tempResp.Price = tempData[y].OrderPrice
|
||||
tempResp.Amount = tempData[y].Amount
|
||||
|
||||
@@ -69,7 +69,7 @@ func (c *COINUT) SeedInstruments() error {
|
||||
}
|
||||
|
||||
for _, y := range i.Instruments {
|
||||
c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstID)
|
||||
c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstrumentID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,21 +35,24 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("Coinut load config error", err)
|
||||
}
|
||||
bConfig, err := cfg.GetExchangeConfig("COINUT")
|
||||
coinutCfg, err := cfg.GetExchangeConfig("COINUT")
|
||||
if err != nil {
|
||||
log.Fatal("Coinut Setup() init error")
|
||||
}
|
||||
bConfig.API.AuthenticatedSupport = true
|
||||
bConfig.API.AuthenticatedWebsocketSupport = true
|
||||
bConfig.API.Credentials.Key = apiKey
|
||||
bConfig.API.Credentials.ClientID = clientID
|
||||
err = c.Setup(bConfig)
|
||||
coinutCfg.API.AuthenticatedSupport = true
|
||||
coinutCfg.API.AuthenticatedWebsocketSupport = true
|
||||
coinutCfg.API.Credentials.Key = apiKey
|
||||
coinutCfg.API.Credentials.ClientID = clientID
|
||||
err = c.Setup(coinutCfg)
|
||||
if err != nil {
|
||||
log.Fatal("Coinut setup error", err)
|
||||
}
|
||||
|
||||
c.SeedInstruments()
|
||||
|
||||
err = c.SeedInstruments()
|
||||
if err != nil {
|
||||
log.Fatal("Coinut setup error ", err)
|
||||
}
|
||||
c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -80,7 +83,7 @@ func setupWSTestAuth(t *testing.T) {
|
||||
}
|
||||
c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go c.WsHandleData()
|
||||
go c.wsReadData()
|
||||
err = c.wsAuthenticate()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -259,7 +262,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
_, err := c.GetActiveOrders(&getOrdersRequest)
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
@@ -270,8 +273,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
func TestGetOrderHistoryWrapper(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.BTC,
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.BTC,
|
||||
currency.USD)},
|
||||
}
|
||||
|
||||
@@ -297,11 +300,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "123",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "123",
|
||||
}
|
||||
response, err := c.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -317,10 +320,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
}
|
||||
currencyPair := currency.NewPair(currency.BTC, currency.USD)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := c.CancelOrder(orderCancellation)
|
||||
@@ -339,10 +342,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := c.CancelAllOrders(orderCancellation)
|
||||
@@ -520,7 +523,7 @@ func TestWsAuthCancelOrdersWrapper(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
orderDetails := order.Cancel{
|
||||
CurrencyPair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
}
|
||||
_, err := c.CancelAllOrders(&orderDetails)
|
||||
if err != nil {
|
||||
@@ -649,3 +652,479 @@ func TestGetNonce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderbook(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"buy":
|
||||
[ { "count": 7, "price": "750.00000000", "qty": "0.07000000" },
|
||||
{ "count": 1, "price": "751.00000000", "qty": "0.01000000" },
|
||||
{ "count": 1, "price": "751.34500000", "qty": "0.01000000" } ],
|
||||
"sell":
|
||||
[ { "count": 6, "price": "750.58100000", "qty": "0.06000000" },
|
||||
{ "count": 1, "price": "750.58200000", "qty": "0.01000000" },
|
||||
{ "count": 1, "price": "750.58300000", "qty": "0.01000000" } ],
|
||||
"inst_id": 1,
|
||||
"nonce": 704114,
|
||||
"total_buy": "67.52345000",
|
||||
"total_sell": "0.08000000",
|
||||
"reply": "inst_order_book",
|
||||
"status": [ "OK" ]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{ "count": 7,
|
||||
"inst_id": 1,
|
||||
"price": "750.58100000",
|
||||
"qty": "0.07000000",
|
||||
"total_buy": "120.06412000",
|
||||
"reply": "inst_order_book_update",
|
||||
"side": "BUY",
|
||||
"trans_id": 169384
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"highest_buy": "750.58100000",
|
||||
"inst_id": 1,
|
||||
"last": "752.00000000",
|
||||
"lowest_sell": "752.00000000",
|
||||
"reply": "inst_tick",
|
||||
"timestamp": 1481355058109705,
|
||||
"trans_id": 170064,
|
||||
"volume": "0.07650000",
|
||||
"volume24": "56.07650000"
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsGetInstruments(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"SPOT":{
|
||||
"LTCBTC":[
|
||||
{
|
||||
"base":"LTC",
|
||||
"inst_id":1,
|
||||
"decimal_places":5,
|
||||
"quote":"BTC"
|
||||
}
|
||||
],
|
||||
"ETHBTC":[
|
||||
{
|
||||
"quote":"BTC",
|
||||
"base":"ETH",
|
||||
"decimal_places":5,
|
||||
"inst_id":2
|
||||
}
|
||||
]
|
||||
},
|
||||
"nonce":39116,
|
||||
"reply":"inst_list",
|
||||
"status":[
|
||||
"OK"
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if c.instrumentMap.LookupID("ETHBTC") != 2 {
|
||||
t.Error("Expected id to load")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrades(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"nonce": 450319,
|
||||
"reply": "inst_trade",
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"trades": [
|
||||
{
|
||||
"price": "750.00000000",
|
||||
"qty": "0.01000000",
|
||||
"side": "BUY",
|
||||
"timestamp": 1481193563288963,
|
||||
"trans_id": 169514
|
||||
},
|
||||
{
|
||||
"price": "750.00000000",
|
||||
"qty": "0.01000000",
|
||||
"side": "BUY",
|
||||
"timestamp": 1481193345279104,
|
||||
"trans_id": 169510
|
||||
},
|
||||
{
|
||||
"price": "750.00000000",
|
||||
"qty": "0.01000000",
|
||||
"side": "BUY",
|
||||
"timestamp": 1481193333272230,
|
||||
"trans_id": 169506
|
||||
},
|
||||
{
|
||||
"price": "750.00000000",
|
||||
"qty": "0.01000000",
|
||||
"side": "BUY",
|
||||
"timestamp": 1481193007342874,
|
||||
"trans_id": 169502
|
||||
}]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"inst_id": 1,
|
||||
"price": "750.58300000",
|
||||
"reply": "inst_trade_update",
|
||||
"side": "BUY",
|
||||
"timestamp": 0,
|
||||
"trans_id": 169478
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsLogin(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"api_key":"b46e658f-d4c4-433c-b032-093423b1aaa4",
|
||||
"country":"NA",
|
||||
"email":"tester@test.com",
|
||||
"failed_times":0,
|
||||
"lang":"en_US",
|
||||
"nonce":829055,
|
||||
"otp_enabled":false,
|
||||
"products_enabled":[
|
||||
"SPOT",
|
||||
"FUTURE",
|
||||
"BINARY_OPTION",
|
||||
"OPTION"
|
||||
],
|
||||
"reply":"login",
|
||||
"session_id":"f8833081-af69-4266-904d-eea088cdcc52",
|
||||
"status":[
|
||||
"OK"
|
||||
],
|
||||
"timezone":"Asia/Singapore",
|
||||
"unverified_email":"",
|
||||
"username":"test"
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsAccountBalance(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"nonce": 306254,
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"BTC": "192.46630415",
|
||||
"LTC": "6000.00000000",
|
||||
"ETC": "800.00000000",
|
||||
"ETH": "496.99938000",
|
||||
"floating_pl": "0.00000000",
|
||||
"initial_margin": "0.00000000",
|
||||
"realized_pl": "0.00000000",
|
||||
"maintenance_margin": "0.00000000",
|
||||
"equity": "192.46630415",
|
||||
"reply": "user_balance",
|
||||
"trans_id": 15159032
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrder(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"nonce":956475,
|
||||
"status":[
|
||||
"OK"
|
||||
],
|
||||
"order_id":1,
|
||||
"open_qty": "0.01",
|
||||
"inst_id": 490590,
|
||||
"qty":"0.01",
|
||||
"client_ord_id": 1345,
|
||||
"order_price":"750.581",
|
||||
"reply":"order_accepted",
|
||||
"side":"SELL",
|
||||
"trans_id":127303
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(` {
|
||||
"commission": {
|
||||
"amount": "0.00799000",
|
||||
"currency": "USD"
|
||||
},
|
||||
"fill_price": "799.00000000",
|
||||
"fill_qty": "0.01000000",
|
||||
"nonce": 956475,
|
||||
"order": {
|
||||
"client_ord_id": 12345,
|
||||
"inst_id": 490590,
|
||||
"open_qty": "0.00000000",
|
||||
"order_id": 721923,
|
||||
"price": "748.00000000",
|
||||
"qty": "0.01000000",
|
||||
"side": "SELL",
|
||||
"timestamp": 1482903034617491
|
||||
},
|
||||
"reply": "order_filled",
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"timestamp": 1482903034617491,
|
||||
"trans_id": 20859252
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(` {
|
||||
"nonce": 275825,
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"order_id": 7171,
|
||||
"open_qty": "100000.00000000",
|
||||
"price": "750.60000000",
|
||||
"inst_id": 490590,
|
||||
"reasons": [
|
||||
"NOT_ENOUGH_BALANCE"
|
||||
],
|
||||
"client_ord_id": 4,
|
||||
"timestamp": 1482080535098689,
|
||||
"reply": "order_rejected",
|
||||
"qty": "100000.00000000",
|
||||
"side": "BUY",
|
||||
"trans_id": 3282993
|
||||
}`)
|
||||
err = c.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("Expected not enough balance error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrders(t *testing.T) {
|
||||
pressXToJSON := []byte(`[
|
||||
{
|
||||
"nonce": 621701,
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"order_id": 331,
|
||||
"open_qty": "0.01000000",
|
||||
"price": "750.58100000",
|
||||
"inst_id": 490590,
|
||||
"client_ord_id": 1345,
|
||||
"timestamp": 1490713990542441,
|
||||
"reply": "order_accepted",
|
||||
"qty": "0.01000000",
|
||||
"side": "SELL",
|
||||
"trans_id": 15155495
|
||||
},
|
||||
{
|
||||
"nonce": 621701,
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"order_id": 332,
|
||||
"open_qty": "0.01000000",
|
||||
"price": "750.32100000",
|
||||
"inst_id": 490590,
|
||||
"client_ord_id": 50001346,
|
||||
"timestamp": 1490713990542441,
|
||||
"reply": "order_accepted",
|
||||
"qty": "0.01000000",
|
||||
"side": "BUY",
|
||||
"trans_id": 15155497
|
||||
}
|
||||
]`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOpenOrders(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"nonce": 1234,
|
||||
"reply": "user_open_orders",
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"orders": [
|
||||
{
|
||||
"order_id": 35,
|
||||
"open_qty": "0.01000000",
|
||||
"price": "750.58200000",
|
||||
"inst_id": 490590,
|
||||
"client_ord_id": 4,
|
||||
"timestamp": 1481138766081720,
|
||||
"qty": "0.01000000",
|
||||
"side": "BUY"
|
||||
},
|
||||
{
|
||||
"order_id": 30,
|
||||
"open_qty": "0.01000000",
|
||||
"price": "750.58100000",
|
||||
"inst_id": 490590,
|
||||
"client_ord_id": 5,
|
||||
"timestamp": 1481137697919617,
|
||||
"qty": "0.01000000",
|
||||
"side": "BUY"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsCancelOrder(t *testing.T) {
|
||||
pressXToJSON := []byte(` {
|
||||
"nonce": 547201,
|
||||
"reply": "cancel_order",
|
||||
"order_id": 1,
|
||||
"client_ord_id": 13556,
|
||||
"status": [
|
||||
"OK"
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsCancelOrders(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"nonce": 547201,
|
||||
"reply": "cancel_orders",
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"order_id": 329,
|
||||
"status": "OK",
|
||||
"inst_id": 490590,
|
||||
"client_ord_id": 13561
|
||||
},
|
||||
{
|
||||
"order_id": 332,
|
||||
"status": "OK",
|
||||
"inst_id": 490590,
|
||||
"client_ord_id": 13562
|
||||
}
|
||||
],
|
||||
"trans_id": 15166063
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderHistory(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"nonce": 326181,
|
||||
"reply": "trade_history",
|
||||
"status": [
|
||||
"OK"
|
||||
],
|
||||
"total_number": 261,
|
||||
"trades": [
|
||||
{
|
||||
"commission": {
|
||||
"amount": "0.00000100",
|
||||
"currency": "BTC"
|
||||
},
|
||||
"order": {
|
||||
"client_ord_id": 297125564,
|
||||
"inst_id": 490590,
|
||||
"open_qty": "0.00000000",
|
||||
"order_id": 721327,
|
||||
"price": "1.00000000",
|
||||
"qty": "0.00100000",
|
||||
"side": "SELL",
|
||||
"timestamp": 1482490337560987
|
||||
},
|
||||
"fill_price": "1.00000000",
|
||||
"fill_qty": "0.00100000",
|
||||
"timestamp": 1482490337560987,
|
||||
"trans_id": 10020695
|
||||
},
|
||||
{
|
||||
"commission": {
|
||||
"amount": "0.00000100",
|
||||
"currency": "BTC"
|
||||
},
|
||||
"order": {
|
||||
"client_ord_id": 297118937,
|
||||
"inst_id": 490590,
|
||||
"open_qty": "0.00000000",
|
||||
"order_id": 721326,
|
||||
"price": "1.00000000",
|
||||
"qty": "0.00100000",
|
||||
"side": "SELL",
|
||||
"timestamp": 1482490330557949
|
||||
},
|
||||
"fill_price": "1.00000000",
|
||||
"fill_qty": "0.00100000",
|
||||
"timestamp": 1482490330557949,
|
||||
"trans_id": 10020514
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := c.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToStatus(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Quantity float64
|
||||
Result order.Status
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "order_accepted", Result: order.Active},
|
||||
{Case: "order_filled", Quantity: 1, Result: order.PartiallyFilled},
|
||||
{Case: "order_rejected", Result: order.Rejected},
|
||||
{Case: "order_filled", Result: order.Filled},
|
||||
{Case: "LOL", Result: order.UnknownStatus},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderStatus(testCases[i].Case, testCases[i].Quantity)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,17 @@ import (
|
||||
|
||||
// GenericResponse is the generic response you will get from coinut
|
||||
type GenericResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// InstrumentBase holds information on base currency
|
||||
type InstrumentBase struct {
|
||||
Base string `json:"base"`
|
||||
DecimalPlaces int `json:"decimal_places"`
|
||||
InstID int64 `json:"inst_id"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
Quote string `json:"quote"`
|
||||
}
|
||||
|
||||
@@ -30,22 +30,22 @@ type Instruments struct {
|
||||
|
||||
// Ticker holds ticker information
|
||||
type Ticker struct {
|
||||
High24 float64 `json:"high24,string"`
|
||||
HighestBuy float64 `json:"highest_buy,string"`
|
||||
InstrumentID int `json:"inst_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low24 float64 `json:"low24,string"`
|
||||
LowestSell float64 `json:"lowest_sell,string"`
|
||||
PrevTransID int64 `json:"prev_trans_id"`
|
||||
PriceChange24 float64 `json:"price_change_24,string"`
|
||||
Reply string `json:"reply"`
|
||||
OpenInterest float64 `json:"open_interest,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
Volume24 float64 `json:"volume24,string"`
|
||||
Volume24Quote float64 `json:"volume24_quote,string"`
|
||||
VolumeQuote float64 `json:"volume_quote,string"`
|
||||
High24 float64 `json:"high24,string"`
|
||||
HighestBuy float64 `json:"highest_buy,string"`
|
||||
InstrumentID int `json:"inst_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low24 float64 `json:"low24,string"`
|
||||
LowestSell float64 `json:"lowest_sell,string"`
|
||||
PreviousTransactionID int64 `json:"prev_trans_id"`
|
||||
PriceChange24 float64 `json:"price_change_24,string"`
|
||||
Reply string `json:"reply"`
|
||||
OpenInterest float64 `json:"open_interest,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
Volume24 float64 `json:"volume24,string"`
|
||||
Volume24Quote float64 `json:"volume24_quote,string"`
|
||||
VolumeQuote float64 `json:"volume_quote,string"`
|
||||
}
|
||||
|
||||
// OrderbookBase is a sub-type holding price and quantity
|
||||
@@ -57,21 +57,21 @@ type OrderbookBase struct {
|
||||
|
||||
// Orderbook is the full order book
|
||||
type Orderbook struct {
|
||||
Buy []OrderbookBase `json:"buy"`
|
||||
Sell []OrderbookBase `json:"sell"`
|
||||
InstrumentID int `json:"inst_id"`
|
||||
TotalBuy float64 `json:"total_buy,string"`
|
||||
TotalSell float64 `json:"total_sell,string"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Buy []OrderbookBase `json:"buy"`
|
||||
Sell []OrderbookBase `json:"sell"`
|
||||
InstrumentID int `json:"inst_id"`
|
||||
TotalBuy float64 `json:"total_buy,string"`
|
||||
TotalSell float64 `json:"total_sell,string"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// TradeBase is a sub-type holding information on trades
|
||||
type TradeBase struct {
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
Side string `json:"side"`
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
Side string `json:"side"`
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// Trades holds the full amount of trades associated with API keys
|
||||
@@ -152,11 +152,11 @@ type OrdersBase struct {
|
||||
|
||||
// GetOpenOrdersResponse holds all order data from GetOpenOrders request
|
||||
type GetOpenOrdersResponse struct {
|
||||
Nonce int `json:"nonce"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
TransID int `json:"trans_id"`
|
||||
Nonce int `json:"nonce"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
TransactionID int `json:"trans_id"`
|
||||
}
|
||||
|
||||
// OrdersResponse holds the full data range on orders
|
||||
@@ -268,12 +268,12 @@ type OpenPosition struct {
|
||||
}
|
||||
|
||||
type wsRequest struct {
|
||||
Request string `json:"request"`
|
||||
SecType string `json:"sec_type,omitempty"`
|
||||
InstID int64 `json:"inst_id,omitempty"`
|
||||
TopN int64 `json:"top_n,omitempty"`
|
||||
Subscribe bool `json:"subscribe,omitempty"`
|
||||
Nonce int64 `json:"nonce,omitempty"`
|
||||
Request string `json:"request"`
|
||||
SecurityType string `json:"sec_type,omitempty"`
|
||||
InstrumentID int64 `json:"inst_id,omitempty"`
|
||||
TopN int64 `json:"top_n,omitempty"`
|
||||
Subscribe bool `json:"subscribe,omitempty"`
|
||||
Nonce int64 `json:"nonce,omitempty"`
|
||||
}
|
||||
|
||||
type wsResponse struct {
|
||||
@@ -419,39 +419,39 @@ type WsCancelOrderParameters struct {
|
||||
|
||||
// WsCancelOrderRequest data required for cancelling an order
|
||||
type WsCancelOrderRequest struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
// WsCancelOrderResponse contains cancelled order data
|
||||
type WsCancelOrderResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
Status []string `json:"status"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
ClientOrderID int64 `json:"client_ord_id"`
|
||||
Status []string `json:"status"`
|
||||
}
|
||||
|
||||
// WsCancelOrdersResponse contains all cancelled order data
|
||||
type WsCancelOrdersResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Results []WsCancelOrdersResponseData `json:"results"`
|
||||
Status []string `json:"status"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Results []WsCancelOrdersResponseData `json:"results"`
|
||||
Status []string `json:"status"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsCancelOrdersResponseData individual cancellation response data
|
||||
type WsCancelOrdersResponseData struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Status string `json:"status"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// WsGetOpenOrdersRequest ws request
|
||||
type WsGetOpenOrdersRequest struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
@@ -463,20 +463,20 @@ type WsSubmitOrdersRequest struct {
|
||||
|
||||
// WsSubmitOrdersRequestData ws request data
|
||||
type WsSubmitOrdersRequestData struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
ClientOrdID int `json:"client_ord_id"`
|
||||
Side string `json:"side"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"qty,string"`
|
||||
ClientOrderID int `json:"client_ord_id"`
|
||||
Side string `json:"side"`
|
||||
}
|
||||
|
||||
// WsSubmitOrderRequest ws request
|
||||
type WsSubmitOrderRequest struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
OrderID int64 `json:"client_ord_id"`
|
||||
Side string `json:"side"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"qty,string"`
|
||||
OrderID int64 `json:"client_ord_id"`
|
||||
Side string `json:"side"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
@@ -490,60 +490,60 @@ type WsSubmitOrderParameters struct {
|
||||
|
||||
// WsUserBalanceResponse ws response
|
||||
type WsUserBalanceResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
Btc float64 `json:"BTC,string"`
|
||||
Ltc float64 `json:"LTC,string"`
|
||||
Etc float64 `json:"ETC,string"`
|
||||
Eth float64 `json:"ETH,string"`
|
||||
FloatingPl float64 `json:"floating_pl,string"`
|
||||
InitialMargin float64 `json:"initial_margin,string"`
|
||||
RealizedPl float64 `json:"realized_pl,string"`
|
||||
MaintenanceMargin float64 `json:"maintenance_margin,string"`
|
||||
Equity float64 `json:"equity,string"`
|
||||
Reply string `json:"reply"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
Btc float64 `json:"BTC,string"`
|
||||
Ltc float64 `json:"LTC,string"`
|
||||
Etc float64 `json:"ETC,string"`
|
||||
Eth float64 `json:"ETH,string"`
|
||||
FloatingProfitLoss float64 `json:"floating_pl,string"`
|
||||
InitialMargin float64 `json:"initial_margin,string"`
|
||||
RealisedProfitLoss float64 `json:"realized_pl,string"`
|
||||
MaintenanceMargin float64 `json:"maintenance_margin,string"`
|
||||
Equity float64 `json:"equity,string"`
|
||||
Reply string `json:"reply"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsOrderAcceptedResponse ws response
|
||||
type WsOrderAcceptedResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OpenQty float64 `json:"open_qty,string"`
|
||||
InstID int64 `json:"inst_id"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
OrderPrice float64 `json:"order_price,string"`
|
||||
Reply string `json:"reply"`
|
||||
Side string `json:"side"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OpenQuantity float64 `json:"open_qty,string"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
Quantity float64 `json:"qty,string"`
|
||||
ClientOrderID int64 `json:"client_ord_id"`
|
||||
OrderPrice float64 `json:"order_price,string"`
|
||||
Reply string `json:"reply"`
|
||||
Side string `json:"side"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsOrderFilledResponse ws response
|
||||
type WsOrderFilledResponse struct {
|
||||
Commission WsOrderFilledCommissionData `json:"commission"`
|
||||
FillPrice float64 `json:"fill_price,string"`
|
||||
FillQty float64 `json:"fill_qty,string"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Order WsOrderData `json:"order"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Commission WsOrderFilledCommissionData `json:"commission"`
|
||||
FillPrice float64 `json:"fill_price,string"`
|
||||
FillQuantity float64 `json:"fill_qty,string"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Order WsOrderData `json:"order"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsOrderData ws response data
|
||||
type WsOrderData struct {
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
InstID int64 `json:"inst_id"`
|
||||
OpenQty float64 `json:"open_qty,string"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
Side string `json:"side"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Status []string `json:"status"`
|
||||
ClientOrderID int64 `json:"client_ord_id"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
OpenQuantity float64 `json:"open_qty,string"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"qty,string"`
|
||||
Side string `json:"side"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Status []string `json:"status"`
|
||||
}
|
||||
|
||||
// WsOrderFilledCommissionData ws response data
|
||||
@@ -554,38 +554,28 @@ type WsOrderFilledCommissionData struct {
|
||||
|
||||
// WsOrderRejectedResponse ws response
|
||||
type WsOrderRejectedResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OpenQty float64 `json:"open_qty,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
InstID int64 `json:"inst_id"`
|
||||
Reasons []string `json:"reasons"`
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Reply string `json:"reply"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
Side string `json:"side"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OpenQuantity float64 `json:"open_qty,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
Reasons []string `json:"reasons"`
|
||||
ClientOrderID int64 `json:"client_ord_id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Reply string `json:"reply"`
|
||||
Quantity float64 `json:"qty,string"`
|
||||
Side string `json:"side"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsStandardOrderResponse a standardised order
|
||||
type WsStandardOrderResponse struct {
|
||||
InstID int64
|
||||
OrderID int64
|
||||
ClientOrdID int64
|
||||
TransID int64
|
||||
Nonce int64
|
||||
Status []string
|
||||
Qty float64
|
||||
OpenQty float64
|
||||
Price float64
|
||||
Side string
|
||||
Reasons []string
|
||||
Timestamp int64
|
||||
OrderType string
|
||||
CommissionAmount float64
|
||||
CommissionCurrency currency.Pair
|
||||
type wsInstList struct {
|
||||
Spot map[string][]struct {
|
||||
Base string `json:"base"`
|
||||
DecimalPlaces int64 `json:"decimal_places"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
Quote string `json:"quote"`
|
||||
} `json:"spot"`
|
||||
}
|
||||
|
||||
// WsUserOpenOrdersResponse ws response
|
||||
@@ -613,12 +603,12 @@ type WsTradeHistoryCommissionData struct {
|
||||
|
||||
// WsTradeHistoryTradeData ws response data
|
||||
type WsTradeHistoryTradeData struct {
|
||||
Commission WsTradeHistoryCommissionData `json:"commission"`
|
||||
Order WsOrderData `json:"order"`
|
||||
FillPrice float64 `json:"fill_price,string"`
|
||||
FillQty float64 `json:"fill_qty,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Commission WsTradeHistoryCommissionData `json:"commission"`
|
||||
Order WsOrderData `json:"order"`
|
||||
FillPrice float64 `json:"fill_price,string"`
|
||||
FillQuantity float64 `json:"fill_qty,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsLoginResponse ws response data
|
||||
@@ -630,9 +620,9 @@ type WsLoginResponse struct {
|
||||
Email string `json:"email"`
|
||||
FailedTimes int64 `json:"failed_times"`
|
||||
KycPassed bool `json:"kyc_passed"`
|
||||
Lang string `json:"lang"`
|
||||
Language string `json:"lang"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
OtpEnabled bool `json:"otp_enabled"`
|
||||
OTPEnabled bool `json:"otp_enabled"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
ProductsEnabled []string `json:"products_enabled"`
|
||||
Referred bool `json:"referred"`
|
||||
@@ -648,10 +638,10 @@ type WsLoginResponse struct {
|
||||
|
||||
// WsNewOrderResponse returns if new_order response failes
|
||||
type WsNewOrderResponse struct {
|
||||
Msg string `json:"msg"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
Message string `json:"msg"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
}
|
||||
|
||||
// WsGetAccountBalanceResponse contains values of each currency
|
||||
@@ -681,3 +671,36 @@ type instrumentMap struct {
|
||||
Loaded bool
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
type wsOrderContainer struct {
|
||||
OrderID int64 `json:"order_id"`
|
||||
ClientOrderID int64 `json:"client_ord_id"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransactionID int64 `json:"trans_id"`
|
||||
OpenQuantity float64 `json:"open_qty,string"`
|
||||
OrderPrice float64 `json:"order_price,string"`
|
||||
Quantity float64 `json:"qty,string"`
|
||||
FillPrice float64 `json:"fill_price,string"`
|
||||
FillQuantity float64 `json:"fill_qty,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
Reply string `json:"reply"`
|
||||
Side string `json:"side"`
|
||||
Status []string `json:"status"`
|
||||
Reasons []string `json:"reasons"`
|
||||
Order struct {
|
||||
ClientOrderID int64 `json:"client_ord_id"`
|
||||
InstrumentID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"qty,string"`
|
||||
OpenQuantity float64 `json:"open_qty,string"`
|
||||
Side string `json:"side"`
|
||||
} `json:"order"`
|
||||
Commission struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Currency string `json:"currency"`
|
||||
} `json:"commission"`
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (c *COINUT) WsConnect() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go c.WsHandleData()
|
||||
go c.wsReadData()
|
||||
|
||||
if !c.instrumentMap.IsLoaded() {
|
||||
_, err = c.WsGetInstruments()
|
||||
@@ -68,8 +68,8 @@ func (c *COINUT) WsConnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles read data
|
||||
func (c *COINUT) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (c *COINUT) wsReadData() {
|
||||
c.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
@@ -98,8 +98,10 @@ func (c *COINUT) WsHandleData() {
|
||||
}
|
||||
for i := range incoming {
|
||||
if incoming[i].Nonce > 0 {
|
||||
c.WebsocketConn.AddResponseWithID(incoming[i].Nonce, resp.Raw)
|
||||
break
|
||||
if c.WebsocketConn.IsIDWaitingForResponse(incoming[i].Nonce) {
|
||||
c.WebsocketConn.SetResponseIDAndData(incoming[i].Nonce, resp.Raw)
|
||||
break
|
||||
}
|
||||
}
|
||||
var individualJSON []byte
|
||||
individualJSON, err = json.Marshal(incoming[i])
|
||||
@@ -107,7 +109,10 @@ func (c *COINUT) WsHandleData() {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
c.wsProcessResponse(individualJSON)
|
||||
err = c.wsHandleData(individualJSON)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var incoming wsResponse
|
||||
@@ -116,31 +121,116 @@ func (c *COINUT) WsHandleData() {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
c.wsProcessResponse(resp.Raw)
|
||||
err = c.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
func (c *COINUT) wsHandleData(respRaw []byte) error {
|
||||
if strings.HasPrefix(string(respRaw), "[") {
|
||||
var orders []wsOrderContainer
|
||||
err := json.Unmarshal(respRaw, &orders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range orders {
|
||||
o, err2 := c.parseOrderContainer(&orders[i])
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
c.Websocket.DataHandler <- o
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var incoming wsResponse
|
||||
err := json.Unmarshal(resp, &incoming)
|
||||
err := json.Unmarshal(respRaw, &incoming)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
if strings.Contains(string(respRaw), "client_ord_id") {
|
||||
if c.WebsocketConn.IsIDWaitingForResponse(incoming.Nonce) {
|
||||
c.WebsocketConn.SetResponseIDAndData(incoming.Nonce, respRaw)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
switch incoming.Reply {
|
||||
case "hb":
|
||||
channels["hb"] <- resp
|
||||
channels["hb"] <- respRaw
|
||||
case "login":
|
||||
var login WsLoginResponse
|
||||
err := json.Unmarshal(respRaw, &login)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if login.APIKey != c.API.Credentials.Key {
|
||||
c.API.AuthenticatedWebsocketSupport = false
|
||||
}
|
||||
case "user_balance":
|
||||
var userBalance WsUserBalanceResponse
|
||||
err := json.Unmarshal(respRaw, &userBalance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "user_open_orders":
|
||||
var openOrders WsUserOpenOrdersResponse
|
||||
err := json.Unmarshal(respRaw, &openOrders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "cancel_order":
|
||||
var cancel WsCancelOrderResponse
|
||||
err := json.Unmarshal(respRaw, &cancel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- &order.Modify{
|
||||
Exchange: c.Name,
|
||||
ID: strconv.FormatInt(cancel.OrderID, 10),
|
||||
Status: order.Cancelled,
|
||||
LastUpdated: time.Now(),
|
||||
}
|
||||
case "cancel_orders":
|
||||
var cancels WsCancelOrdersResponse
|
||||
err := json.Unmarshal(respRaw, &cancels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range cancels.Results {
|
||||
c.Websocket.DataHandler <- &order.Modify{
|
||||
Exchange: c.Name,
|
||||
ID: strconv.FormatInt(cancels.Results[i].OrderID, 10),
|
||||
Status: order.Cancelled,
|
||||
LastUpdated: time.Now(),
|
||||
}
|
||||
}
|
||||
case "trade_history":
|
||||
var trades WsTradeHistoryResponse
|
||||
err := json.Unmarshal(respRaw, &trades)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "inst_list":
|
||||
var instList wsInstList
|
||||
err := json.Unmarshal(respRaw, &instList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range instList.Spot {
|
||||
for _, v2 := range v {
|
||||
c.instrumentMap.Seed(k, v2.InstrumentID)
|
||||
}
|
||||
}
|
||||
case "inst_tick":
|
||||
var wsTicker WsTicker
|
||||
err := json.Unmarshal(resp, &wsTicker)
|
||||
err := json.Unmarshal(respRaw, &wsTicker)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
currencyPair := c.instrumentMap.LookupInstrument(wsTicker.InstID)
|
||||
c.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: c.Name,
|
||||
@@ -157,20 +247,17 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
c.GetEnabledPairs(asset.Spot),
|
||||
c.GetPairFormat(asset.Spot, true)),
|
||||
}
|
||||
|
||||
case "inst_order_book":
|
||||
var orderbooksnapshot WsOrderbookSnapshot
|
||||
err := json.Unmarshal(resp, &orderbooksnapshot)
|
||||
var orderbookSnapshot WsOrderbookSnapshot
|
||||
err := json.Unmarshal(respRaw, &orderbookSnapshot)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
err = c.WsProcessOrderbookSnapshot(&orderbooksnapshot)
|
||||
err = c.WsProcessOrderbookSnapshot(&orderbookSnapshot)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
currencyPair := c.instrumentMap.LookupInstrument(orderbooksnapshot.InstID)
|
||||
currencyPair := c.instrumentMap.LookupInstrument(orderbookSnapshot.InstID)
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: c.Name,
|
||||
Asset: asset.Spot,
|
||||
@@ -180,15 +267,13 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
}
|
||||
case "inst_order_book_update":
|
||||
var orderbookUpdate WsOrderbookUpdate
|
||||
err := json.Unmarshal(resp, &orderbookUpdate)
|
||||
err := json.Unmarshal(respRaw, &orderbookUpdate)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
err = c.WsProcessOrderbookUpdate(&orderbookUpdate)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
currencyPair := c.instrumentMap.LookupInstrument(orderbookUpdate.InstID)
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
@@ -200,20 +285,25 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
}
|
||||
case "inst_trade":
|
||||
var tradeSnap WsTradeSnapshot
|
||||
err := json.Unmarshal(resp, &tradeSnap)
|
||||
err := json.Unmarshal(respRaw, &tradeSnap)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
case "inst_trade_update":
|
||||
var tradeUpdate WsTradeUpdate
|
||||
err := json.Unmarshal(resp, &tradeUpdate)
|
||||
err := json.Unmarshal(respRaw, &tradeUpdate)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
currencyPair := c.instrumentMap.LookupInstrument(tradeUpdate.InstID)
|
||||
tSide, err := order.StringToOrderSide(tradeUpdate.Side)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
c.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(tradeUpdate.Timestamp, 0),
|
||||
CurrencyPair: currency.NewPairFromFormattedPairs(currencyPair,
|
||||
@@ -222,24 +312,137 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
AssetType: asset.Spot,
|
||||
Exchange: c.Name,
|
||||
Price: tradeUpdate.Price,
|
||||
Side: tradeUpdate.Side,
|
||||
Side: tSide,
|
||||
}
|
||||
case "order_filled", "order_rejected", "order_accepted":
|
||||
var orderContainer wsOrderContainer
|
||||
err := json.Unmarshal(respRaw, &orderContainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o, err := c.parseOrderContainer(&orderContainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Websocket.DataHandler <- o
|
||||
default:
|
||||
if incoming.Nonce > 0 {
|
||||
c.WebsocketConn.AddResponseWithID(incoming.Nonce, resp)
|
||||
return
|
||||
}
|
||||
c.Websocket.DataHandler <- fmt.Errorf("%v unhandled websocket response: %s", c.Name, resp)
|
||||
c.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: c.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringToOrderStatus(status string, quantity float64) (order.Status, error) {
|
||||
switch status {
|
||||
case "order_accepted":
|
||||
return order.Active, nil
|
||||
case "order_filled":
|
||||
if quantity > 0 {
|
||||
return order.PartiallyFilled, nil
|
||||
}
|
||||
return order.Filled, nil
|
||||
case "order_rejected":
|
||||
return order.Rejected, nil
|
||||
default:
|
||||
return order.UnknownStatus, errors.New(status + " not recognised as order status")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *COINUT) parseOrderContainer(oContainer *wsOrderContainer) (*order.Detail, error) {
|
||||
var oSide order.Side
|
||||
var oStatus order.Status
|
||||
var err error
|
||||
var orderID = strconv.FormatInt(oContainer.OrderID, 10)
|
||||
if oContainer.Side != "" {
|
||||
oSide, err = order.StringToOrderSide(oContainer.Side)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
} else if oContainer.Order.Side != "" {
|
||||
oSide, err = order.StringToOrderSide(oContainer.Order.Side)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oStatus, err = stringToOrderStatus(oContainer.Reply, oContainer.OpenQuantity)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if oContainer.Status[0] != "OK" {
|
||||
return nil, fmt.Errorf("%s - Order rejected: %v", c.Name, oContainer.Status)
|
||||
}
|
||||
if len(oContainer.Reasons) > 0 {
|
||||
return nil, fmt.Errorf("%s - Order rejected: %v", c.Name, oContainer.Reasons)
|
||||
}
|
||||
|
||||
o := &order.Detail{
|
||||
Price: oContainer.Price,
|
||||
Amount: oContainer.Quantity,
|
||||
ExecutedAmount: oContainer.FillQuantity,
|
||||
RemainingAmount: oContainer.OpenQuantity,
|
||||
Exchange: c.Name,
|
||||
ID: orderID,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
Date: time.Unix(0, oContainer.Timestamp),
|
||||
Trades: nil,
|
||||
}
|
||||
if oContainer.Reply == "order_filled" {
|
||||
o.Side, err = order.StringToOrderSide(oContainer.Order.Side)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: c.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
o.RemainingAmount = oContainer.Order.OpenQuantity
|
||||
o.Amount = oContainer.Order.Quantity
|
||||
o.ID = strconv.FormatInt(oContainer.Order.OrderID, 10)
|
||||
o.LastUpdated = time.Unix(0, oContainer.Timestamp)
|
||||
o.Pair, o.AssetType, err = c.GetRequestFormattedPairAndAssetType(c.instrumentMap.LookupInstrument(oContainer.Order.InstrumentID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Trades = []order.TradeHistory{
|
||||
{
|
||||
Price: oContainer.FillPrice,
|
||||
Amount: oContainer.FillQuantity,
|
||||
Exchange: c.Name,
|
||||
TID: strconv.FormatInt(oContainer.TransactionID, 10),
|
||||
Side: oSide,
|
||||
Timestamp: time.Unix(0, oContainer.Timestamp),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
o.Pair, o.AssetType, err = c.GetRequestFormattedPairAndAssetType(c.instrumentMap.LookupInstrument(oContainer.InstrumentID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// WsGetInstruments fetches instrument list and propagates a local cache
|
||||
func (c *COINUT) WsGetInstruments() (Instruments, error) {
|
||||
var list Instruments
|
||||
request := wsRequest{
|
||||
Request: "inst_list",
|
||||
SecType: strings.ToUpper(asset.Spot.String()),
|
||||
Nonce: getNonce(),
|
||||
Request: "inst_list",
|
||||
SecurityType: strings.ToUpper(asset.Spot.String()),
|
||||
Nonce: getNonce(),
|
||||
}
|
||||
resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request)
|
||||
if err != nil {
|
||||
@@ -250,7 +453,7 @@ func (c *COINUT) WsGetInstruments() (Instruments, error) {
|
||||
return list, err
|
||||
}
|
||||
for curr, data := range list.Instruments {
|
||||
c.instrumentMap.Seed(curr, data[0].InstID)
|
||||
c.instrumentMap.Seed(curr, data[0].InstrumentID)
|
||||
}
|
||||
if len(c.instrumentMap.GetInstrumentIDs()) == 0 {
|
||||
return list, errors.New("instrument list failed to populate")
|
||||
@@ -330,7 +533,7 @@ func (c *COINUT) GenerateDefaultSubscriptions() {
|
||||
func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
subscribe := wsRequest{
|
||||
Request: channelToSubscribe.Channel,
|
||||
InstID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency,
|
||||
InstrumentID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency,
|
||||
asset.Spot).String()),
|
||||
Subscribe: true,
|
||||
Nonce: getNonce(),
|
||||
@@ -342,7 +545,7 @@ func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip
|
||||
func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
subscribe := wsRequest{
|
||||
Request: channelToSubscribe.Channel,
|
||||
InstID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency,
|
||||
InstrumentID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency,
|
||||
asset.Spot).String()),
|
||||
Subscribe: false,
|
||||
Nonce: getNonce(),
|
||||
@@ -426,7 +629,7 @@ func (c *COINUT) wsGetAccountBalance() (*UserBalance, error) {
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*WsStandardOrderResponse, error) {
|
||||
func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*order.Detail, error) {
|
||||
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return nil, fmt.Errorf("%v not authorised to submit order", c.Name)
|
||||
}
|
||||
@@ -434,8 +637,8 @@ func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*WsStandardOrderResp
|
||||
var orderSubmissionRequest WsSubmitOrderRequest
|
||||
orderSubmissionRequest.Request = "new_order"
|
||||
orderSubmissionRequest.Nonce = getNonce()
|
||||
orderSubmissionRequest.InstID = c.instrumentMap.LookupID(curr)
|
||||
orderSubmissionRequest.Qty = o.Amount
|
||||
orderSubmissionRequest.InstrumentID = c.instrumentMap.LookupID(curr)
|
||||
orderSubmissionRequest.Quantity = o.Amount
|
||||
orderSubmissionRequest.Price = o.Price
|
||||
orderSubmissionRequest.Side = string(o.Side)
|
||||
|
||||
@@ -446,107 +649,36 @@ func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*WsStandardOrderResp
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var standardOrder WsStandardOrderResponse
|
||||
standardOrder, err = c.wsStandardiseOrderResponse(resp)
|
||||
var incoming wsOrderContainer
|
||||
err = json.Unmarshal(resp, &incoming)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if standardOrder.Status[0] != "OK" {
|
||||
return &standardOrder, fmt.Errorf("%v order submission failed. %v", c.Name, standardOrder)
|
||||
}
|
||||
if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" {
|
||||
return &standardOrder, fmt.Errorf("%v order submission failed. %v", c.Name, standardOrder.Reasons[0])
|
||||
}
|
||||
return &standardOrder, nil
|
||||
}
|
||||
|
||||
func (c *COINUT) wsStandardiseOrderResponse(resp []byte) (WsStandardOrderResponse, error) {
|
||||
var response WsStandardOrderResponse
|
||||
var incoming wsResponse
|
||||
err := json.Unmarshal(resp, &incoming)
|
||||
var ord *order.Detail
|
||||
ord, err = c.parseOrderContainer(&incoming)
|
||||
if err != nil {
|
||||
return response, err
|
||||
return nil, err
|
||||
}
|
||||
switch incoming.Reply {
|
||||
case "order_accepted":
|
||||
var orderAccepted WsOrderAcceptedResponse
|
||||
err := json.Unmarshal(resp, &orderAccepted)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
response = WsStandardOrderResponse{
|
||||
InstID: orderAccepted.InstID,
|
||||
Nonce: orderAccepted.Nonce,
|
||||
OpenQty: orderAccepted.OpenQty,
|
||||
OrderID: orderAccepted.OrderID,
|
||||
OrderType: orderAccepted.Reply,
|
||||
Price: orderAccepted.OrderPrice,
|
||||
Qty: orderAccepted.Qty,
|
||||
Side: orderAccepted.Side,
|
||||
Status: orderAccepted.Status,
|
||||
TransID: orderAccepted.TransID,
|
||||
ClientOrdID: orderAccepted.ClientOrdID,
|
||||
}
|
||||
case "order_filled":
|
||||
var orderFilled WsOrderFilledResponse
|
||||
err := json.Unmarshal(resp, &orderFilled)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
response = WsStandardOrderResponse{
|
||||
InstID: orderFilled.Order.InstID,
|
||||
Nonce: orderFilled.Nonce,
|
||||
OpenQty: orderFilled.Order.OpenQty,
|
||||
OrderID: orderFilled.Order.OrderID,
|
||||
OrderType: orderFilled.Reply,
|
||||
Price: orderFilled.Order.Price,
|
||||
Qty: orderFilled.Order.Qty,
|
||||
Side: orderFilled.Order.Side,
|
||||
Status: orderFilled.Status,
|
||||
TransID: orderFilled.TransID,
|
||||
ClientOrdID: orderFilled.Order.ClientOrdID,
|
||||
}
|
||||
case "order_rejected":
|
||||
var orderRejected WsOrderRejectedResponse
|
||||
err := json.Unmarshal(resp, &orderRejected)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
response = WsStandardOrderResponse{
|
||||
InstID: orderRejected.InstID,
|
||||
Nonce: orderRejected.Nonce,
|
||||
OpenQty: orderRejected.OpenQty,
|
||||
OrderID: orderRejected.OrderID,
|
||||
OrderType: orderRejected.Reply,
|
||||
Price: orderRejected.Price,
|
||||
Qty: orderRejected.Qty,
|
||||
Side: orderRejected.Side,
|
||||
Status: orderRejected.Status,
|
||||
TransID: orderRejected.TransID,
|
||||
ClientOrdID: orderRejected.ClientOrdID,
|
||||
Reasons: orderRejected.Reasons,
|
||||
}
|
||||
}
|
||||
return response, nil
|
||||
return ord, nil
|
||||
}
|
||||
|
||||
func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardOrderResponse, []error) {
|
||||
var errors []error
|
||||
var ordersResponse []WsStandardOrderResponse
|
||||
func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]order.Detail, []error) {
|
||||
var errs []error
|
||||
var ordersResponse []order.Detail
|
||||
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
errors = append(errors, fmt.Errorf("%v not authorised to submit orders", c.Name))
|
||||
return nil, errors
|
||||
errs = append(errs, fmt.Errorf("%v not authorised to submit orders", c.Name))
|
||||
return nil, errs
|
||||
}
|
||||
orderRequest := WsSubmitOrdersRequest{}
|
||||
for i := range orders {
|
||||
curr := c.FormatExchangeCurrency(orders[i].Currency, asset.Spot).String()
|
||||
orderRequest.Orders = append(orderRequest.Orders,
|
||||
WsSubmitOrdersRequestData{
|
||||
Qty: orders[i].Amount,
|
||||
Price: orders[i].Price,
|
||||
Side: string(orders[i].Side),
|
||||
InstID: c.instrumentMap.LookupID(curr),
|
||||
ClientOrdID: i + 1,
|
||||
Quantity: orders[i].Amount,
|
||||
Price: orders[i].Price,
|
||||
Side: string(orders[i].Side),
|
||||
InstrumentID: c.instrumentMap.LookupID(curr),
|
||||
ClientOrderID: i + 1,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -554,44 +686,25 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO
|
||||
orderRequest.Request = "new_orders"
|
||||
resp, err := c.WebsocketConn.SendMessageReturnResponse(orderRequest.Nonce, orderRequest)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
return nil, errors
|
||||
errs = append(errs, err)
|
||||
return nil, errs
|
||||
}
|
||||
var incoming []interface{}
|
||||
var incoming []wsOrderContainer
|
||||
err = json.Unmarshal(resp, &incoming)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
return nil, errors
|
||||
errs = append(errs, err)
|
||||
return nil, errs
|
||||
}
|
||||
for i := range incoming {
|
||||
var individualJSON []byte
|
||||
individualJSON, err = json.Marshal(incoming[i])
|
||||
o, err := c.parseOrderContainer(&incoming[i])
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
standardOrder, err := c.wsStandardiseOrderResponse(individualJSON)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
if standardOrder.Status[0] != "OK" {
|
||||
errors = append(errors, fmt.Errorf("%v order submission failed. %v", c.Name, standardOrder))
|
||||
continue
|
||||
}
|
||||
if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" {
|
||||
errors = append(errors, fmt.Errorf("%v order submission failed for currency %v and orderID %v, message %v ",
|
||||
c.Name,
|
||||
c.instrumentMap.LookupInstrument(standardOrder.InstID),
|
||||
standardOrder.OrderID,
|
||||
standardOrder.Reasons[0]))
|
||||
|
||||
continue
|
||||
}
|
||||
ordersResponse = append(ordersResponse, standardOrder)
|
||||
ordersResponse = append(ordersResponse, *o)
|
||||
}
|
||||
|
||||
return ordersResponse, errors
|
||||
return ordersResponse, errs
|
||||
}
|
||||
|
||||
func (c *COINUT) wsGetOpenOrders(curr string) (*WsUserOpenOrdersResponse, error) {
|
||||
@@ -602,7 +715,7 @@ func (c *COINUT) wsGetOpenOrders(curr string) (*WsUserOpenOrdersResponse, error)
|
||||
var openOrdersRequest WsGetOpenOrdersRequest
|
||||
openOrdersRequest.Request = "user_open_orders"
|
||||
openOrdersRequest.Nonce = getNonce()
|
||||
openOrdersRequest.InstID = c.instrumentMap.LookupID(curr)
|
||||
openOrdersRequest.InstrumentID = c.instrumentMap.LookupID(curr)
|
||||
|
||||
resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest)
|
||||
if err != nil {
|
||||
@@ -628,7 +741,7 @@ func (c *COINUT) wsCancelOrder(cancellation *WsCancelOrderParameters) (*CancelOr
|
||||
curr := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String()
|
||||
var cancellationRequest WsCancelOrderRequest
|
||||
cancellationRequest.Request = "cancel_order"
|
||||
cancellationRequest.InstID = c.instrumentMap.LookupID(curr)
|
||||
cancellationRequest.InstrumentID = c.instrumentMap.LookupID(curr)
|
||||
cancellationRequest.OrderID = cancellation.OrderID
|
||||
cancellationRequest.Nonce = getNonce()
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ func (c *COINUT) FetchTradablePairs(asset asset.Item) ([]string, error) {
|
||||
instruments = resp.Instruments
|
||||
var pairs []string
|
||||
for i := range instruments {
|
||||
c.instrumentMap.Seed(instruments[i][0].Base+instruments[i][0].Quote, instruments[i][0].InstID)
|
||||
c.instrumentMap.Seed(instruments[i][0].Base+instruments[i][0].Quote, instruments[i][0].InstrumentID)
|
||||
p := instruments[i][0].Base + c.GetPairFormat(asset, false).Delimiter + instruments[i][0].Quote
|
||||
pairs = append(pairs, p)
|
||||
}
|
||||
@@ -481,17 +481,17 @@ func (c *COINUT) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
var response *WsStandardOrderResponse
|
||||
var response *order.Detail
|
||||
response, err = c.wsSubmitOrder(&WsSubmitOrderParameters{
|
||||
Currency: o.Pair,
|
||||
Side: o.OrderSide,
|
||||
Side: o.Side,
|
||||
Amount: o.Amount,
|
||||
Price: o.Price,
|
||||
})
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10)
|
||||
submitOrderResponse.OrderID = response.ID
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
} else {
|
||||
err = c.loadInstrumentsIfNotLoaded()
|
||||
@@ -507,7 +507,7 @@ func (c *COINUT) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
|
||||
var APIResponse interface{}
|
||||
var clientIDInt uint64
|
||||
isBuyOrder := o.OrderSide == order.Buy
|
||||
isBuyOrder := o.Side == order.Buy
|
||||
clientIDInt, err = strconv.ParseUint(o.ClientID, 0, 32)
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
@@ -550,26 +550,26 @@ func (c *COINUT) CancelOrder(o *order.Cancel) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderIDInt, err := strconv.ParseInt(o.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(o.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(
|
||||
o.CurrencyPair,
|
||||
o.Pair,
|
||||
asset.Spot).String(),
|
||||
)
|
||||
if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
var resp *CancelOrdersResponse
|
||||
resp, err = c.wsCancelOrder(&WsCancelOrderParameters{
|
||||
Currency: o.CurrencyPair,
|
||||
Currency: o.Pair,
|
||||
OrderID: orderIDInt,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.Status) >= 1 && resp.Status[0] != "OK" {
|
||||
return errors.New(c.Name + " - Failed to cancel order " + o.OrderID)
|
||||
return errors.New(c.Name + " - Failed to cancel order " + o.ID)
|
||||
}
|
||||
} else {
|
||||
if currencyID == 0 {
|
||||
@@ -593,15 +593,15 @@ func (c *COINUT) CancelAllOrders(details *order.Cancel) (order.CancelAllResponse
|
||||
}
|
||||
cancelAllOrdersResponse.Status = make(map[string]string)
|
||||
if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
openOrders, err := c.wsGetOpenOrders(details.CurrencyPair.String())
|
||||
openOrders, err := c.wsGetOpenOrders(details.Pair.String())
|
||||
if err != nil {
|
||||
return cancelAllOrdersResponse, err
|
||||
}
|
||||
var ordersToCancel []WsCancelOrderParameters
|
||||
for i := range openOrders.Orders {
|
||||
if openOrders.Orders[i].InstID == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.CurrencyPair, asset.Spot).String()) {
|
||||
if openOrders.Orders[i].InstrumentID == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.Pair, asset.Spot).String()) {
|
||||
ordersToCancel = append(ordersToCancel, WsCancelOrderParameters{
|
||||
Currency: details.CurrencyPair,
|
||||
Currency: details.Pair,
|
||||
OrderID: openOrders.Orders[i].OrderID,
|
||||
})
|
||||
}
|
||||
@@ -619,7 +619,7 @@ func (c *COINUT) CancelAllOrders(details *order.Cancel) (order.CancelAllResponse
|
||||
var allTheOrders []OrderResponse
|
||||
ids := c.instrumentMap.GetInstrumentIDs()
|
||||
for x := range ids {
|
||||
if ids[x] == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.CurrencyPair, asset.Spot).String()) {
|
||||
if ids[x] == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.Pair, asset.Spot).String()) {
|
||||
openOrders, err := c.GetOpenOrders(ids[x])
|
||||
if err != nil {
|
||||
return cancelAllOrdersResponse, err
|
||||
@@ -704,9 +704,9 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
}
|
||||
var orders []order.Detail
|
||||
var currenciesToCheck []string
|
||||
if len(req.Currencies) == 0 {
|
||||
for i := range req.Currencies {
|
||||
currenciesToCheck = append(currenciesToCheck, c.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String())
|
||||
if len(req.Pairs) == 0 {
|
||||
for i := range req.Pairs {
|
||||
currenciesToCheck = append(currenciesToCheck, c.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String())
|
||||
}
|
||||
} else {
|
||||
for k := range c.instrumentMap.Instruments {
|
||||
@@ -723,32 +723,27 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orders = append(orders, order.Detail{
|
||||
Exchange: c.Name,
|
||||
ID: strconv.FormatInt(openOrders.Orders[i].OrderID, 10),
|
||||
CurrencyPair: c.FormatExchangeCurrency(currency.NewPairFromString(currenciesToCheck[x]), asset.Spot),
|
||||
OrderSide: order.Side(openOrders.Orders[i].Side),
|
||||
OrderDate: time.Unix(0, openOrders.Orders[i].Timestamp),
|
||||
Pair: c.FormatExchangeCurrency(currency.NewPairFromString(currenciesToCheck[x]), asset.Spot),
|
||||
Side: order.Side(openOrders.Orders[i].Side),
|
||||
Date: time.Unix(0, openOrders.Orders[i].Timestamp),
|
||||
Status: order.Active,
|
||||
Price: openOrders.Orders[i].Price,
|
||||
Amount: openOrders.Orders[i].Qty,
|
||||
ExecutedAmount: openOrders.Orders[i].Qty - openOrders.Orders[i].OpenQty,
|
||||
RemainingAmount: openOrders.Orders[i].OpenQty,
|
||||
Amount: openOrders.Orders[i].Quantity,
|
||||
ExecutedAmount: openOrders.Orders[i].Quantity - openOrders.Orders[i].OpenQuantity,
|
||||
RemainingAmount: openOrders.Orders[i].OpenQuantity,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var instrumentsToUse []int64
|
||||
if len(req.Currencies) > 0 {
|
||||
for x := range req.Currencies {
|
||||
curr := c.FormatExchangeCurrency(req.Currencies[x],
|
||||
asset.Spot).String()
|
||||
instrumentsToUse = append(instrumentsToUse,
|
||||
c.instrumentMap.LookupID(curr))
|
||||
}
|
||||
} else {
|
||||
instrumentsToUse = c.instrumentMap.GetInstrumentIDs()
|
||||
for x := range req.Pairs {
|
||||
curr := c.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String()
|
||||
instrumentsToUse = append(instrumentsToUse,
|
||||
c.instrumentMap.LookupID(curr))
|
||||
}
|
||||
|
||||
if len(instrumentsToUse) == 0 {
|
||||
return nil, errors.New("no instrument IDs to use")
|
||||
instrumentsToUse = c.instrumentMap.GetInstrumentIDs()
|
||||
}
|
||||
|
||||
for x := range instrumentsToUse {
|
||||
@@ -764,20 +759,20 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orderSide := order.Side(strings.ToUpper(openOrders.Orders[y].Side))
|
||||
orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0)
|
||||
orders = append(orders, order.Detail{
|
||||
ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10),
|
||||
Amount: openOrders.Orders[y].Quantity,
|
||||
Price: openOrders.Orders[y].Price,
|
||||
Exchange: c.Name,
|
||||
OrderSide: orderSide,
|
||||
OrderDate: orderDate,
|
||||
CurrencyPair: p,
|
||||
ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10),
|
||||
Amount: openOrders.Orders[y].Quantity,
|
||||
Price: openOrders.Orders[y].Price,
|
||||
Exchange: c.Name,
|
||||
Side: orderSide,
|
||||
Date: orderDate,
|
||||
Pair: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -790,25 +785,25 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
}
|
||||
var allOrders []order.Detail
|
||||
if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
for i := range req.Currencies {
|
||||
for i := range req.Pairs {
|
||||
for j := int64(0); ; j += 100 {
|
||||
trades, err := c.wsGetTradeHistory(req.Currencies[i], j, 100)
|
||||
trades, err := c.wsGetTradeHistory(req.Pairs[i], j, 100)
|
||||
if err != nil {
|
||||
return allOrders, err
|
||||
}
|
||||
for x := range trades.Trades {
|
||||
curr := c.instrumentMap.LookupInstrument(trades.Trades[x].InstID)
|
||||
curr := c.instrumentMap.LookupInstrument(trades.Trades[x].InstrumentID)
|
||||
allOrders = append(allOrders, order.Detail{
|
||||
Exchange: c.Name,
|
||||
ID: strconv.FormatInt(trades.Trades[x].OrderID, 10),
|
||||
CurrencyPair: currency.NewPairFromString(curr),
|
||||
OrderSide: order.Side(trades.Trades[x].Side),
|
||||
OrderDate: time.Unix(0, trades.Trades[x].Timestamp),
|
||||
Pair: currency.NewPairFromString(curr),
|
||||
Side: order.Side(trades.Trades[x].Side),
|
||||
Date: time.Unix(0, trades.Trades[x].Timestamp),
|
||||
Status: order.Filled,
|
||||
Price: trades.Trades[x].Price,
|
||||
Amount: trades.Trades[x].Qty,
|
||||
ExecutedAmount: trades.Trades[x].Qty,
|
||||
RemainingAmount: trades.Trades[x].OpenQty,
|
||||
Amount: trades.Trades[x].Quantity,
|
||||
ExecutedAmount: trades.Trades[x].Quantity,
|
||||
RemainingAmount: trades.Trades[x].OpenQuantity,
|
||||
})
|
||||
}
|
||||
if len(trades.Trades) < 100 {
|
||||
@@ -818,21 +813,16 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
}
|
||||
} else {
|
||||
var instrumentsToUse []int64
|
||||
if len(req.Currencies) > 0 {
|
||||
for x := range req.Currencies {
|
||||
curr := c.FormatExchangeCurrency(req.Currencies[x],
|
||||
asset.Spot).String()
|
||||
instrumentID := c.instrumentMap.LookupID(curr)
|
||||
if instrumentID > 0 {
|
||||
instrumentsToUse = append(instrumentsToUse, instrumentID)
|
||||
}
|
||||
for x := range req.Pairs {
|
||||
curr := c.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String()
|
||||
instrumentID := c.instrumentMap.LookupID(curr)
|
||||
if instrumentID > 0 {
|
||||
instrumentsToUse = append(instrumentsToUse, instrumentID)
|
||||
}
|
||||
} else {
|
||||
instrumentsToUse = c.instrumentMap.GetInstrumentIDs()
|
||||
}
|
||||
|
||||
if len(instrumentsToUse) == 0 {
|
||||
return nil, errors.New("no instrument IDs to use")
|
||||
instrumentsToUse = c.instrumentMap.GetInstrumentIDs()
|
||||
}
|
||||
for x := range instrumentsToUse {
|
||||
orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1)
|
||||
@@ -847,20 +837,20 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orderSide := order.Side(strings.ToUpper(orders.Trades[y].Order.Side))
|
||||
orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0)
|
||||
allOrders = append(allOrders, order.Detail{
|
||||
ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10),
|
||||
Amount: orders.Trades[y].Order.Quantity,
|
||||
Price: orders.Trades[y].Order.Price,
|
||||
Exchange: c.Name,
|
||||
OrderSide: orderSide,
|
||||
OrderDate: orderDate,
|
||||
CurrencyPair: p,
|
||||
ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10),
|
||||
Amount: orders.Trades[y].Order.Quantity,
|
||||
Price: orders.Trades[y].Order.Price,
|
||||
Exchange: c.Name,
|
||||
Side: orderSide,
|
||||
Date: orderDate,
|
||||
Pair: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&allOrders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&allOrders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&allOrders, req.Side)
|
||||
return allOrders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ const (
|
||||
// DefaultHTTPTimeout is the default HTTP/HTTPS Timeout for exchange requests
|
||||
DefaultHTTPTimeout = time.Second * 15
|
||||
// DefaultWebsocketResponseCheckTimeout is the default delay in checking for an expected websocket response
|
||||
DefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
|
||||
DefaultWebsocketResponseCheckTimeout = time.Millisecond * 50
|
||||
// DefaultWebsocketResponseMaxLimit is the default max wait for an expected websocket response before a timeout
|
||||
DefaultWebsocketResponseMaxLimit = time.Second * 7
|
||||
// DefaultWebsocketOrderbookBufferLimit is the maximum number of orderbook updates that get stored before being applied
|
||||
@@ -213,9 +213,10 @@ func (e *Base) GetAssetTypes() asset.Items {
|
||||
|
||||
// GetPairAssetType returns the associated asset type for the currency pair
|
||||
func (e *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) {
|
||||
for i := range e.GetAssetTypes() {
|
||||
if e.GetEnabledPairs(e.GetAssetTypes()[i]).Contains(c, true) {
|
||||
return e.GetAssetTypes()[i], nil
|
||||
assetTypes := e.GetAssetTypes()
|
||||
for i := range assetTypes {
|
||||
if e.GetEnabledPairs(assetTypes[i]).Contains(c, true) {
|
||||
return assetTypes[i], nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("asset type not associated with currency pair")
|
||||
@@ -340,6 +341,24 @@ func (e *Base) GetEnabledPairs(assetType asset.Item) currency.Pairs {
|
||||
return pairs.Format(format.Delimiter, format.Index, format.Uppercase)
|
||||
}
|
||||
|
||||
// GetRequestFormattedPairAndAssetType is a method that returns the enabled currency pair of
|
||||
// along with its asset type. Only use when there is no chance of the same name crossing over
|
||||
func (e *Base) GetRequestFormattedPairAndAssetType(p string) (currency.Pair, asset.Item, error) {
|
||||
assetTypes := e.GetAssetTypes()
|
||||
var response currency.Pair
|
||||
for i := range assetTypes {
|
||||
format := e.GetPairFormat(assetTypes[i], true)
|
||||
pairs := e.CurrencyPairs.GetPairs(assetTypes[i], true)
|
||||
for j := range pairs {
|
||||
formattedPair := pairs[j].Format(format.Delimiter, format.Uppercase)
|
||||
if strings.EqualFold(formattedPair.String(), p) {
|
||||
return formattedPair, assetTypes[i], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return response, "", errors.New("pair not found: " + p)
|
||||
}
|
||||
|
||||
// GetAvailablePairs is a method that returns the available currency pairs
|
||||
// of the exchange by asset type
|
||||
func (e *Base) GetAvailablePairs(assetType asset.Item) currency.Pairs {
|
||||
|
||||
@@ -1445,3 +1445,39 @@ func TestGetAssetType(t *testing.T) {
|
||||
t.Error("should be spot but is", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFormattedPairAndAssetType(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{
|
||||
Config: &config.ExchangeConfig{},
|
||||
}
|
||||
b.SetCurrencyPairFormat()
|
||||
b.Config.CurrencyPairs.UseGlobalFormat = true
|
||||
b.CurrencyPairs.UseGlobalFormat = true
|
||||
pFmt := ¤cy.PairFormat{
|
||||
Delimiter: "#",
|
||||
}
|
||||
b.CurrencyPairs.RequestFormat = pFmt
|
||||
b.CurrencyPairs.ConfigFormat = pFmt
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Enabled: currency.Pairs{
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
},
|
||||
}
|
||||
b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot}
|
||||
p, a, err := b.GetRequestFormattedPairAndAssetType("btc#usd")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if p.String() != "btc#usd" {
|
||||
t.Error("Expected pair to match")
|
||||
}
|
||||
if a != asset.Spot {
|
||||
t.Error("Expected spot asset")
|
||||
}
|
||||
_, _, err = b.GetRequestFormattedPairAndAssetType("btcusd")
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ type FeeBuilder struct {
|
||||
// TradeHistory holds exchange history data
|
||||
type TradeHistory struct {
|
||||
Timestamp time.Time
|
||||
TID int64
|
||||
TID string
|
||||
Price float64
|
||||
Amount float64
|
||||
Exchange string
|
||||
|
||||
@@ -262,7 +262,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := e.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -275,11 +275,11 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
currPair := currency.NewPair(currency.BTC, currency.USD)
|
||||
currPair.Delimiter = "_"
|
||||
getOrdersRequest.Currencies = []currency.Pair{currPair}
|
||||
getOrdersRequest.Pairs = []currency.Pair{currPair}
|
||||
|
||||
_, err := e.GetOrderHistory(&getOrdersRequest)
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
@@ -306,11 +306,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := e.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -327,10 +327,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := e.CancelOrder(orderCancellation)
|
||||
@@ -349,10 +349,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := e.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -345,11 +345,11 @@ func (e *EXMO) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
var oT string
|
||||
switch s.OrderType {
|
||||
switch s.Type {
|
||||
case order.Limit:
|
||||
return submitOrderResponse, errors.New("unsupported order type")
|
||||
case order.Market:
|
||||
if s.OrderSide == order.Sell {
|
||||
if s.Side == order.Sell {
|
||||
oT = "market_sell"
|
||||
} else {
|
||||
oT = "market_buy"
|
||||
@@ -368,7 +368,7 @@ func (e *EXMO) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
return submitOrderResponse, nil
|
||||
@@ -382,7 +382,7 @@ func (e *EXMO) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (e *EXMO) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -484,31 +484,31 @@ func (e *EXMO) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err
|
||||
orderDate := time.Unix(resp[i].Created, 0)
|
||||
orderSide := order.Side(strings.ToUpper(resp[i].Type))
|
||||
orders = append(orders, order.Detail{
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
Amount: resp[i].Quantity,
|
||||
OrderDate: orderDate,
|
||||
Price: resp[i].Price,
|
||||
OrderSide: orderSide,
|
||||
Exchange: e.Name,
|
||||
CurrencyPair: symbol,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
Amount: resp[i].Quantity,
|
||||
Date: orderDate,
|
||||
Price: resp[i].Price,
|
||||
Side: orderSide,
|
||||
Exchange: e.Name,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("currency must be supplied")
|
||||
}
|
||||
|
||||
var allTrades []UserTrades
|
||||
for i := range req.Currencies {
|
||||
resp, err := e.GetUserTrades(e.FormatExchangeCurrency(req.Currencies[i], asset.Spot).String(), "", "10000")
|
||||
for i := range req.Pairs {
|
||||
resp, err := e.GetUserTrades(e.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String(), "", "10000")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -523,18 +523,18 @@ func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, err
|
||||
orderDate := time.Unix(allTrades[i].Date, 0)
|
||||
orderSide := order.Side(strings.ToUpper(allTrades[i].Type))
|
||||
orders = append(orders, order.Detail{
|
||||
ID: strconv.FormatInt(allTrades[i].TradeID, 10),
|
||||
Amount: allTrades[i].Quantity,
|
||||
OrderDate: orderDate,
|
||||
Price: allTrades[i].Price,
|
||||
OrderSide: orderSide,
|
||||
Exchange: e.Name,
|
||||
CurrencyPair: symbol,
|
||||
ID: strconv.FormatInt(allTrades[i].TradeID, 10),
|
||||
Amount: allTrades[i].Quantity,
|
||||
Date: orderDate,
|
||||
Price: allTrades[i].Price,
|
||||
Side: orderSide,
|
||||
Exchange: e.Name,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := g.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -287,12 +287,12 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
currPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
currPair.Delimiter = "_"
|
||||
getOrdersRequest.Currencies = []currency.Pair{currPair}
|
||||
getOrdersRequest.Pairs = []currency.Pair{currPair}
|
||||
|
||||
_, err := g.GetOrderHistory(&getOrdersRequest)
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
@@ -319,11 +319,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.LTC,
|
||||
Quote: currency.BTC,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := g.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -340,10 +340,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := g.CancelOrder(orderCancellation)
|
||||
@@ -362,10 +362,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := g.CancelAllOrders(orderCancellation)
|
||||
@@ -497,9 +497,7 @@ func TestWsGetBalance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go g.WsHandleData()
|
||||
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go g.wsReadData()
|
||||
resp, err := g.wsServerSignIn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -535,9 +533,7 @@ func TestWsGetOrderInfo(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go g.WsHandleData()
|
||||
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go g.wsReadData()
|
||||
resp, err := g.wsServerSignIn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -568,15 +564,29 @@ func setupWSTestAuth(t *testing.T) {
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := g.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
|
||||
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go g.WsHandleData()
|
||||
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go g.wsReadData()
|
||||
wsSetupRan = true
|
||||
}
|
||||
|
||||
// TestWsUnsubscribe dials websocket, sends an unsubscribe request.
|
||||
func TestWsUnsubscribe(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
g.Verbose = true
|
||||
err := g.Unsubscribe(wshandler.WebsocketChannelSubscription{
|
||||
Channel: "ticker.subscribe",
|
||||
Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsSubscribe dials websocket, sends a subscribe request.
|
||||
func TestWsSubscribe(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
@@ -589,13 +599,146 @@ func TestWsSubscribe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsUnsubscribe dials websocket, sends an unsubscribe request.
|
||||
func TestWsUnsubscribe(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
err := g.Unsubscribe(wshandler.WebsocketChannelSubscription{
|
||||
Channel: "ticker.subscribe",
|
||||
Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"),
|
||||
})
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "ticker.update",
|
||||
"params":
|
||||
[
|
||||
"BTC_USDT",
|
||||
{
|
||||
"period": 86400,
|
||||
"open": "0",
|
||||
"close": "0",
|
||||
"high": "0",
|
||||
"low": "0",
|
||||
"last": "0.2844",
|
||||
"change": "0",
|
||||
"quoteVolume": "0",
|
||||
"baseVolume": "0"
|
||||
}
|
||||
],
|
||||
"id": null
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrade(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "trades.update",
|
||||
"params":
|
||||
[
|
||||
"BTC_USDT",
|
||||
[
|
||||
{
|
||||
"id": 7172173,
|
||||
"time": 1523339279.761838,
|
||||
"price": "398.59",
|
||||
"amount": "0.027",
|
||||
"type": "buy"
|
||||
}
|
||||
]
|
||||
],
|
||||
"id": null
|
||||
}
|
||||
`)
|
||||
err := g.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsDepth(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "depth.update",
|
||||
"params": [
|
||||
true,
|
||||
{
|
||||
"asks": [
|
||||
[
|
||||
"8000.00",
|
||||
"9.6250"
|
||||
]
|
||||
],
|
||||
"bids": [
|
||||
[
|
||||
"8000.00",
|
||||
"9.6250"
|
||||
]
|
||||
]
|
||||
},
|
||||
"BTC_USDT"
|
||||
],
|
||||
"id": null
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsKLine(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "kline.update",
|
||||
"params":
|
||||
[
|
||||
[
|
||||
1492358400,
|
||||
"7000.00",
|
||||
"8000.0",
|
||||
"8100.00",
|
||||
"6800.00",
|
||||
"1000.00",
|
||||
"123456.00",
|
||||
"BTC_USDT"
|
||||
]
|
||||
],
|
||||
"id": null
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "order.update",
|
||||
"params": [
|
||||
3,
|
||||
{
|
||||
"id": 34628963,
|
||||
"market": "BTC_USDT",
|
||||
"orderType": 1,
|
||||
"type": 2,
|
||||
"user": 602123,
|
||||
"ctime": 1523013969.6271579,
|
||||
"mtime": 1523013969.6271579,
|
||||
"price": "0.1",
|
||||
"amount": "1000",
|
||||
"left": "1000",
|
||||
"filledAmount": "0",
|
||||
"filledTotal": "0",
|
||||
"dealFee": "0"
|
||||
}
|
||||
],
|
||||
"id": null
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsBalanceUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"method": "balance.update",
|
||||
"params": [{"EOS": {"available": "96.765323611874", "freeze": "11"}}],
|
||||
"id": 1234
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -488,3 +488,15 @@ type WsGetBalanceResponseData struct {
|
||||
Available float64 `json:"available,string"`
|
||||
Freeze float64 `json:"freeze,string"`
|
||||
}
|
||||
|
||||
type wsBalanceSubscription struct {
|
||||
Method string `json:"method"`
|
||||
Parameters []map[string]WsGetBalanceResponseData `json:"params"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type wsOrderUpdate struct {
|
||||
ID int64 `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params []interface{} `json:"params"`
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -36,7 +38,7 @@ func (g *Gateio) WsConnect() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go g.WsHandleData()
|
||||
go g.wsReadData()
|
||||
_, err = g.wsServerSignIn()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", g.Name, err)
|
||||
@@ -76,9 +78,8 @@ func (g *Gateio) wsServerSignIn() (*WebsocketAuthenticationResponse, error) {
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// WsHandleData handles all the websocket data coming from the websocket
|
||||
// connection
|
||||
func (g *Gateio) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (g *Gateio) wsReadData() {
|
||||
g.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
@@ -97,203 +98,326 @@ func (g *Gateio) WsHandleData() {
|
||||
return
|
||||
}
|
||||
g.Websocket.TrafficAlert <- struct{}{}
|
||||
var result WebsocketResponse
|
||||
err = json.Unmarshal(resp.Raw, &result)
|
||||
err = g.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
if result.ID > 0 {
|
||||
g.WebsocketConn.AddResponseWithID(result.ID, resp.Raw)
|
||||
continue
|
||||
}
|
||||
|
||||
if result.Error.Code != 0 {
|
||||
if strings.Contains(result.Error.Message, "authentication") {
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v - authentication failed: %v", g.Name, err)
|
||||
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v error %s",
|
||||
g.Name, result.Error.Message)
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(result.Method, "ticker"):
|
||||
var wsTicker WebsocketTicker
|
||||
var c string
|
||||
err = json.Unmarshal(result.Params[1], &wsTicker)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result.Params[0], &c)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
g.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: g.Name,
|
||||
Open: wsTicker.Open,
|
||||
Close: wsTicker.Close,
|
||||
Volume: wsTicker.BaseVolume,
|
||||
QuoteVolume: wsTicker.QuoteVolume,
|
||||
High: wsTicker.High,
|
||||
Low: wsTicker.Low,
|
||||
Last: wsTicker.Last,
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromString(c),
|
||||
}
|
||||
|
||||
case strings.Contains(result.Method, "trades"):
|
||||
var trades []WebsocketTrade
|
||||
var c string
|
||||
err = json.Unmarshal(result.Params[1], &trades)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result.Params[0], &c)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range trades {
|
||||
g.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Now(),
|
||||
CurrencyPair: currency.NewPairFromString(c),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
Price: trades[i].Price,
|
||||
Amount: trades[i].Amount,
|
||||
Side: trades[i].Type,
|
||||
}
|
||||
}
|
||||
|
||||
case strings.Contains(result.Method, "depth"):
|
||||
var IsSnapshot bool
|
||||
var c string
|
||||
var data = make(map[string][][]string)
|
||||
err = json.Unmarshal(result.Params[0], &IsSnapshot)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result.Params[2], &c)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result.Params[1], &data)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
var asks, bids []orderbook.Item
|
||||
|
||||
askData, askOk := data["asks"]
|
||||
for i := range askData {
|
||||
amount, _ := strconv.ParseFloat(askData[i][1], 64)
|
||||
price, _ := strconv.ParseFloat(askData[i][0], 64)
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
bidData, bidOk := data["bids"]
|
||||
for i := range bidData {
|
||||
amount, _ := strconv.ParseFloat(bidData[i][1], 64)
|
||||
price, _ := strconv.ParseFloat(bidData[i][0], 64)
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
if !askOk && !bidOk {
|
||||
g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access ask or bid data")
|
||||
}
|
||||
|
||||
if IsSnapshot {
|
||||
if !askOk {
|
||||
g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access ask data")
|
||||
}
|
||||
|
||||
if !bidOk {
|
||||
g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access bid data")
|
||||
}
|
||||
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.Pair = currency.NewPairFromString(c)
|
||||
newOrderBook.ExchangeName = g.Name
|
||||
|
||||
err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
} else {
|
||||
err = g.Websocket.Orderbook.Update(
|
||||
&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Pair: currency.NewPairFromString(c),
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
|
||||
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currency.NewPairFromString(c),
|
||||
Asset: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
}
|
||||
|
||||
case strings.Contains(result.Method, "kline"):
|
||||
var data []interface{}
|
||||
err = json.Unmarshal(result.Params[0], &data)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
open, _ := strconv.ParseFloat(data[1].(string), 64)
|
||||
closePrice, _ := strconv.ParseFloat(data[2].(string), 64)
|
||||
high, _ := strconv.ParseFloat(data[3].(string), 64)
|
||||
low, _ := strconv.ParseFloat(data[4].(string), 64)
|
||||
volume, _ := strconv.ParseFloat(data[5].(string), 64)
|
||||
|
||||
g.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Now(),
|
||||
Pair: currency.NewPairFromString(data[7].(string)),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
OpenPrice: open,
|
||||
ClosePrice: closePrice,
|
||||
HighPrice: high,
|
||||
LowPrice: low,
|
||||
Volume: volume,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gateio) wsHandleData(respRaw []byte) error {
|
||||
var result WebsocketResponse
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.ID > 0 {
|
||||
if g.WebsocketConn.IsIDWaitingForResponse(result.ID) {
|
||||
g.WebsocketConn.SetResponseIDAndData(result.ID, respRaw)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if result.Error.Code != 0 {
|
||||
if strings.Contains(result.Error.Message, "authentication") {
|
||||
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
return fmt.Errorf("%v - authentication failed: %v", g.Name, err)
|
||||
}
|
||||
return fmt.Errorf("%v error %s",
|
||||
g.Name, result.Error.Message)
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(result.Method, "ticker"):
|
||||
var wsTicker WebsocketTicker
|
||||
var c string
|
||||
err = json.Unmarshal(result.Params[1], &wsTicker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(result.Params[0], &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: g.Name,
|
||||
Open: wsTicker.Open,
|
||||
Close: wsTicker.Close,
|
||||
Volume: wsTicker.BaseVolume,
|
||||
QuoteVolume: wsTicker.QuoteVolume,
|
||||
High: wsTicker.High,
|
||||
Low: wsTicker.Low,
|
||||
Last: wsTicker.Last,
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromString(c),
|
||||
}
|
||||
|
||||
case strings.Contains(result.Method, "trades"):
|
||||
var trades []WebsocketTrade
|
||||
var c string
|
||||
err = json.Unmarshal(result.Params[1], &trades)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(result.Params[0], &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range trades {
|
||||
var tSide order.Side
|
||||
tSide, err = order.StringToOrderSide(trades[i].Type)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: g.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
g.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Now(),
|
||||
CurrencyPair: currency.NewPairFromString(c),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
Price: trades[i].Price,
|
||||
Amount: trades[i].Amount,
|
||||
Side: tSide,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result.Method, "balance.update"):
|
||||
var balance wsBalanceSubscription
|
||||
err = json.Unmarshal(respRaw, &balance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- balance
|
||||
case strings.Contains(result.Method, "order.update"):
|
||||
var orderUpdate wsOrderUpdate
|
||||
err = json.Unmarshal(respRaw, &orderUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invalidJSON := orderUpdate.Params[1].(map[string]interface{})
|
||||
oStatus := order.UnknownStatus
|
||||
oType := order.UnknownType
|
||||
oSide := order.UnknownSide
|
||||
switch orderUpdate.Params[0].(float64) {
|
||||
case 1:
|
||||
oStatus = order.New
|
||||
case 2:
|
||||
oStatus = order.PartiallyFilled
|
||||
case 3:
|
||||
oStatus = order.Filled
|
||||
}
|
||||
switch invalidJSON["orderType"].(float64) {
|
||||
case 1:
|
||||
oType = order.Limit
|
||||
case 2:
|
||||
oType = order.Market
|
||||
}
|
||||
switch invalidJSON["type"].(float64) {
|
||||
case 1:
|
||||
oSide = order.Sell
|
||||
case 2:
|
||||
oSide = order.Buy
|
||||
}
|
||||
var cTime, cTimeDec, mTime, mTimeDec int64
|
||||
var price, amount, filledTotal, left, fee float64
|
||||
cTime, cTimeDec, err = convert.SplitFloatDecimals(invalidJSON["ctime"].(float64))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mTime, mTimeDec, err = convert.SplitFloatDecimals(invalidJSON["mtime"].(float64))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
price, err = strconv.ParseFloat(invalidJSON["price"].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount, err = strconv.ParseFloat(invalidJSON["amount"].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filledTotal, err = strconv.ParseFloat(invalidJSON["filledTotal"].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
left, err = strconv.ParseFloat(invalidJSON["left"].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fee, err = strconv.ParseFloat(invalidJSON["dealFee"].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := currency.NewPairFromString(invalidJSON["market"].(string))
|
||||
var a asset.Item
|
||||
a, err = g.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- &order.Detail{
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
ExecutedAmount: filledTotal,
|
||||
RemainingAmount: left,
|
||||
Fee: fee,
|
||||
Exchange: g.Name,
|
||||
ID: strconv.FormatFloat(invalidJSON["id"].(float64), 'f', -1, 64),
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: time.Unix(cTime, cTimeDec),
|
||||
LastUpdated: time.Unix(mTime, mTimeDec),
|
||||
Pair: p,
|
||||
}
|
||||
case strings.Contains(result.Method, "depth"):
|
||||
var IsSnapshot bool
|
||||
var c string
|
||||
var data = make(map[string][][]string)
|
||||
err = json.Unmarshal(result.Params[0], &IsSnapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result.Params[2], &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result.Params[1], &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var asks, bids []orderbook.Item
|
||||
askData, askOk := data["asks"]
|
||||
for i := range askData {
|
||||
var amount, price float64
|
||||
amount, err = strconv.ParseFloat(askData[i][1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
price, err = strconv.ParseFloat(askData[i][0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
bidData, bidOk := data["bids"]
|
||||
for i := range bidData {
|
||||
var amount, price float64
|
||||
amount, err = strconv.ParseFloat(bidData[i][1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
price, err = strconv.ParseFloat(bidData[i][0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
if !askOk && !bidOk {
|
||||
g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access ask or bid data")
|
||||
}
|
||||
|
||||
if IsSnapshot {
|
||||
if !askOk {
|
||||
g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access ask data")
|
||||
}
|
||||
|
||||
if !bidOk {
|
||||
g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access bid data")
|
||||
}
|
||||
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.Pair = currency.NewPairFromString(c)
|
||||
newOrderBook.ExchangeName = g.Name
|
||||
|
||||
err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = g.Websocket.Orderbook.Update(
|
||||
&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Pair: currency.NewPairFromString(c),
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currency.NewPairFromString(c),
|
||||
Asset: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
}
|
||||
|
||||
case strings.Contains(result.Method, "kline"):
|
||||
var data []interface{}
|
||||
err = json.Unmarshal(result.Params[0], &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
open, err := strconv.ParseFloat(data[1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
closePrice, err := strconv.ParseFloat(data[2].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
high, err := strconv.ParseFloat(data[3].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
low, err := strconv.ParseFloat(data[4].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
volume, err := strconv.ParseFloat(data[5].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Now(),
|
||||
Pair: currency.NewPairFromString(data[7].(string)),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
OpenPrice: open,
|
||||
ClosePrice: closePrice,
|
||||
HighPrice: high,
|
||||
LowPrice: low,
|
||||
Volume: volume,
|
||||
}
|
||||
default:
|
||||
g.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: g.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateAuthenticatedSubscriptions Adds authenticated subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (g *Gateio) GenerateAuthenticatedSubscriptions() {
|
||||
if !g.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
|
||||
@@ -422,7 +422,7 @@ func (g *Gateio) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
var orderTypeFormat string
|
||||
if s.OrderSide == order.Buy {
|
||||
if s.Side == order.Buy {
|
||||
orderTypeFormat = order.Buy.Lower()
|
||||
} else {
|
||||
orderTypeFormat = order.Sell.Lower()
|
||||
@@ -458,12 +458,12 @@ func (g *Gateio) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (g *Gateio) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = g.CancelExistingOrder(orderIDInt,
|
||||
g.FormatExchangeCurrency(order.CurrencyPair, order.AssetType).String())
|
||||
g.FormatExchangeCurrency(order.Pair, order.AssetType).String())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -508,15 +508,15 @@ func (g *Gateio) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
orderDetail.RemainingAmount = orders.Orders[x].InitialAmount - orders.Orders[x].FilledAmount
|
||||
orderDetail.ExecutedAmount = orders.Orders[x].FilledAmount
|
||||
orderDetail.Amount = orders.Orders[x].InitialAmount
|
||||
orderDetail.OrderDate = time.Unix(orders.Orders[x].Timestamp, 0)
|
||||
orderDetail.Date = time.Unix(orders.Orders[x].Timestamp, 0)
|
||||
orderDetail.Status = order.Status(orders.Orders[x].Status)
|
||||
orderDetail.Price = orders.Orders[x].Rate
|
||||
orderDetail.CurrencyPair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair,
|
||||
orderDetail.Pair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair,
|
||||
g.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
if strings.EqualFold(orders.Orders[x].Type, order.Ask.String()) {
|
||||
orderDetail.OrderSide = order.Ask
|
||||
orderDetail.Side = order.Ask
|
||||
} else if strings.EqualFold(orders.Orders[x].Type, order.Bid.String()) {
|
||||
orderDetail.OrderSide = order.Buy
|
||||
orderDetail.Side = order.Buy
|
||||
}
|
||||
return orderDetail, nil
|
||||
}
|
||||
@@ -573,12 +573,12 @@ func (g *Gateio) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error)
|
||||
func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var orders []order.Detail
|
||||
var currPair string
|
||||
if len(req.Currencies) == 1 {
|
||||
currPair = req.Currencies[0].String()
|
||||
if len(req.Pairs) == 1 {
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
if g.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
for i := 0; ; i += 100 {
|
||||
resp, err := g.wsGetOrderInfo(req.OrderType.String(), i, 100)
|
||||
resp, err := g.wsGetOrderInfo(req.Type.String(), i, 100)
|
||||
if err != nil {
|
||||
return orders, err
|
||||
}
|
||||
@@ -601,10 +601,10 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
Exchange: g.Name,
|
||||
AccountID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].User, 10),
|
||||
ID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].ID, 10),
|
||||
CurrencyPair: currency.NewPairFromString(resp.WebSocketOrderQueryRecords[j].Market),
|
||||
OrderSide: orderSide,
|
||||
OrderType: orderType,
|
||||
OrderDate: orderDate,
|
||||
Pair: currency.NewPairFromString(resp.WebSocketOrderQueryRecords[j].Market),
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Date: orderDate,
|
||||
Price: resp.WebSocketOrderQueryRecords[j].Price,
|
||||
Amount: resp.WebSocketOrderQueryRecords[j].Amount,
|
||||
ExecutedAmount: resp.WebSocketOrderQueryRecords[j].FilledAmount,
|
||||
@@ -636,16 +636,16 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
Amount: resp.Orders[i].Amount,
|
||||
Price: resp.Orders[i].Rate,
|
||||
RemainingAmount: resp.Orders[i].FilledAmount,
|
||||
OrderDate: orderDate,
|
||||
OrderSide: side,
|
||||
Date: orderDate,
|
||||
Side: side,
|
||||
Exchange: g.Name,
|
||||
CurrencyPair: symbol,
|
||||
Pair: symbol,
|
||||
Status: order.Status(resp.Orders[i].Status),
|
||||
})
|
||||
}
|
||||
}
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -653,8 +653,8 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
// Can Limit response to specific order status
|
||||
func (g *Gateio) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var trades []TradesResponse
|
||||
for i := range req.Currencies {
|
||||
resp, err := g.GetTradeHistory(req.Currencies[i].String())
|
||||
for i := range req.Pairs {
|
||||
resp, err := g.GetTradeHistory(req.Pairs[i].String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -668,18 +668,18 @@ func (g *Gateio) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
side := order.Side(strings.ToUpper(trade.Type))
|
||||
orderDate := time.Unix(trade.TimeUnix, 0)
|
||||
orders = append(orders, order.Detail{
|
||||
ID: strconv.FormatInt(trade.OrderID, 10),
|
||||
Amount: trade.Amount,
|
||||
Price: trade.Rate,
|
||||
OrderDate: orderDate,
|
||||
OrderSide: side,
|
||||
Exchange: g.Name,
|
||||
CurrencyPair: symbol,
|
||||
ID: strconv.FormatInt(trade.OrderID, 10),
|
||||
Amount: trade.Amount,
|
||||
Price: trade.Rate,
|
||||
Date: orderDate,
|
||||
Side: side,
|
||||
Exchange: g.Name,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ func TestMain(m *testing.M) {
|
||||
log.Fatal("Gemini setup error", err)
|
||||
}
|
||||
g.API.Endpoints.URL = geminiSandboxAPIURL
|
||||
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
log.Printf(sharedtestvalues.LiveTesting, g.Name, g.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ func TestMain(m *testing.M) {
|
||||
|
||||
g.HTTPClient = newClient
|
||||
g.API.Endpoints.URL = serverDetails
|
||||
|
||||
g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
log.Printf(sharedtestvalues.MockTesting, g.Name, g.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -349,8 +349,8 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{
|
||||
currency.NewPair(currency.LTC, currency.BTC),
|
||||
},
|
||||
}
|
||||
@@ -369,8 +369,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)},
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.LTC, currency.BTC)},
|
||||
}
|
||||
|
||||
_, err := g.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -402,11 +402,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.LTC,
|
||||
Quote: currency.BTC,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 10,
|
||||
Amount: 1,
|
||||
ClientID: "1234234",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 10,
|
||||
Amount: 1,
|
||||
ClientID: "1234234",
|
||||
}
|
||||
|
||||
response, err := g.SubmitOrder(orderSubmission)
|
||||
@@ -426,7 +426,7 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "266029865",
|
||||
ID: "266029865",
|
||||
}
|
||||
|
||||
err := g.CancelOrder(orderCancellation)
|
||||
@@ -448,10 +448,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := g.CancelAllOrders(orderCancellation)
|
||||
@@ -553,7 +553,7 @@ func TestWsAuth(t *testing.T) {
|
||||
t.Skip(wshandler.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
go g.WsHandleData()
|
||||
go g.wsReadData()
|
||||
err := g.WsSecureSubscribe(&dialer, geminiWsOrderEvents)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -569,3 +569,495 @@ func TestWsAuth(t *testing.T) {
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
func TestWsMissingRole(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"result":"error",
|
||||
"reason":"MissingRole",
|
||||
"message":"To access this endpoint, you need to log in to the website and go to the settings page to assign one of these roles [FundManager] to API key wujB3szN54gtJ4QDhqRJ which currently has roles [Trader]"
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderEventSubscriptionResponse(t *testing.T) {
|
||||
pressXToJSON := []byte(`[ {
|
||||
"type" : "accepted",
|
||||
"order_id" : "372456298",
|
||||
"event_id" : "372456299",
|
||||
"client_order_id": "20170208_example",
|
||||
"api_session" : "AeRLptFXoYEqLaNiRwv8",
|
||||
"symbol" : "btcusd",
|
||||
"side" : "buy",
|
||||
"order_type" : "exchange limit",
|
||||
"timestamp" : "1478203017",
|
||||
"timestampms" : 1478203017455,
|
||||
"is_live" : true,
|
||||
"is_cancelled" : false,
|
||||
"is_hidden" : false,
|
||||
"avg_execution_price" : "0",
|
||||
"original_amount" : "14.0296",
|
||||
"price" : "1059.54"
|
||||
} ]`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`[{
|
||||
"type": "accepted",
|
||||
"order_id": "109535951",
|
||||
"event_id": "109535952",
|
||||
"api_session": "UI",
|
||||
"symbol": "btcusd",
|
||||
"side": "buy",
|
||||
"order_type": "exchange limit",
|
||||
"timestamp": "1547742904",
|
||||
"timestampms": 1547742904989,
|
||||
"is_live": true,
|
||||
"is_cancelled": false,
|
||||
"is_hidden": false,
|
||||
"original_amount": "1",
|
||||
"price": "3592.00",
|
||||
"socket_sequence": 13
|
||||
}]`)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`[{
|
||||
"type": "accepted",
|
||||
"order_id": "109964529",
|
||||
"event_id": "109964530",
|
||||
"api_session": "UI",
|
||||
"symbol": "btcusd",
|
||||
"side": "buy",
|
||||
"order_type": "market buy",
|
||||
"timestamp": "1547756076",
|
||||
"timestampms": 1547756076644,
|
||||
"is_live": false,
|
||||
"is_cancelled": false,
|
||||
"is_hidden": false,
|
||||
"total_spend": "200.00",
|
||||
"socket_sequence": 29
|
||||
}]`)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`[{
|
||||
"type": "accepted",
|
||||
"order_id": "109964616",
|
||||
"event_id": "109964617",
|
||||
"api_session": "UI",
|
||||
"symbol": "btcusd",
|
||||
"side": "sell",
|
||||
"order_type": "market sell",
|
||||
"timestamp": "1547756893",
|
||||
"timestampms": 1547756893937,
|
||||
"is_live": true,
|
||||
"is_cancelled": false,
|
||||
"is_hidden": false,
|
||||
"original_amount": "25",
|
||||
"socket_sequence": 26
|
||||
}]`)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`[ {
|
||||
"type" : "accepted",
|
||||
"order_id" : "6321",
|
||||
"event_id" : "6322",
|
||||
"api_session" : "UI",
|
||||
"symbol" : "btcusd",
|
||||
"side" : "sell",
|
||||
"order_type" : "block_trade",
|
||||
"timestamp" : "1478204198",
|
||||
"timestampms" : 1478204198989,
|
||||
"is_live" : true,
|
||||
"is_cancelled" : false,
|
||||
"is_hidden" : true,
|
||||
"avg_execution_price" : "0",
|
||||
"original_amount" : "500",
|
||||
"socket_sequence" : 32307
|
||||
} ]`)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSubAck(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "subscription_ack",
|
||||
"accountId": 5365,
|
||||
"subscriptionId": "ws-order-events-5365-b8bk32clqeb13g9tk8p0",
|
||||
"symbolFilter": [
|
||||
"btcusd"
|
||||
],
|
||||
"apiSessionFilter": [
|
||||
"UI"
|
||||
],
|
||||
"eventTypeFilter": [
|
||||
"fill",
|
||||
"closed"
|
||||
]
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsHeartbeat(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "heartbeat",
|
||||
"timestampms": 1547742998508,
|
||||
"sequence": 31,
|
||||
"trace_id": "b8biknoqppr32kc7gfgg",
|
||||
"socket_sequence": 37
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsUnsubscribe(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "unsubscribe",
|
||||
"subscriptions": [{
|
||||
"name": "l2",
|
||||
"symbols": [
|
||||
"BTCUSD",
|
||||
"ETHBTC"
|
||||
]},
|
||||
{"name": "candles_1m",
|
||||
"symbols": [
|
||||
"BTCUSD",
|
||||
"ETHBTC"
|
||||
]}
|
||||
]
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTradeData(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "update",
|
||||
"eventId": 5375547515,
|
||||
"timestamp": 1547760288,
|
||||
"timestampms": 1547760288001,
|
||||
"socket_sequence": 15,
|
||||
"events": [
|
||||
{
|
||||
"type": "trade",
|
||||
"tid": 5375547515,
|
||||
"price": "3632.54",
|
||||
"amount": "0.1362819142",
|
||||
"makerSide": "ask"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsAuctionData(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"eventId": 371469414,
|
||||
"socket_sequence":4009,
|
||||
"timestamp":1486501200,
|
||||
"timestampms":1486501200000,
|
||||
"events": [
|
||||
{
|
||||
"amount": "1406",
|
||||
"makerSide": "auction",
|
||||
"price": "1048.75",
|
||||
"tid": 371469414,
|
||||
"type": "trade"
|
||||
},
|
||||
{
|
||||
"auction_price": "1048.75",
|
||||
"auction_quantity": "1406",
|
||||
"eid": 371469414,
|
||||
"highest_bid_price": "1050.98",
|
||||
"lowest_ask_price": "1050.99",
|
||||
"result": "success",
|
||||
"time_ms": 1486501200000,
|
||||
"type": "auction_result"
|
||||
}
|
||||
],
|
||||
"type": "update"
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsBlockTrade(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type":"update",
|
||||
"eventId":1111597035,
|
||||
"socket_sequence":8,
|
||||
"timestamp":1501175027,
|
||||
"timestampms":1501175027304,
|
||||
"events":[
|
||||
{
|
||||
"type":"block_trade",
|
||||
"tid":1111597035,
|
||||
"price":"10100.00",
|
||||
"amount":"1000"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsCandles(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "candles_15m_updates",
|
||||
"symbol": "BTCUSD",
|
||||
"changes": [
|
||||
[
|
||||
1561054500000,
|
||||
9350.18,
|
||||
9358.35,
|
||||
9350.18,
|
||||
9355.51,
|
||||
2.07
|
||||
],
|
||||
[
|
||||
1561053600000,
|
||||
9357.33,
|
||||
9357.33,
|
||||
9350.18,
|
||||
9350.18,
|
||||
1.5900161
|
||||
]
|
||||
]
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsAuctions(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"eventId": 372481811,
|
||||
"socket_sequence":23,
|
||||
"timestamp": 1486591200,
|
||||
"timestampms": 1486591200000,
|
||||
"events": [
|
||||
{
|
||||
"auction_open_ms": 1486591200000,
|
||||
"auction_time_ms": 1486674000000,
|
||||
"first_indicative_ms": 1486673400000,
|
||||
"last_cancel_time_ms": 1486673985000,
|
||||
"type": "auction_open"
|
||||
}
|
||||
],
|
||||
"type": "update"
|
||||
}`)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "update",
|
||||
"eventId": 2248762586,
|
||||
"timestamp": 1510865640,
|
||||
"timestampms": 1510865640122,
|
||||
"socket_sequence": 177,
|
||||
"events": [
|
||||
{
|
||||
"type": "auction_indicative",
|
||||
"eid": 2248762586,
|
||||
"result": "success",
|
||||
"time_ms": 1510865640000,
|
||||
"highest_bid_price": "7730.69",
|
||||
"lowest_ask_price": "7730.7",
|
||||
"collar_price": "7730.695",
|
||||
"indicative_price": "7750",
|
||||
"indicative_quantity": "45.43325086"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "update",
|
||||
"eventId": 2248795680,
|
||||
"timestamp": 1510866000,
|
||||
"timestampms": 1510866000095,
|
||||
"socket_sequence": 2920,
|
||||
"events": [
|
||||
{
|
||||
"type": "trade",
|
||||
"tid": 2248795680,
|
||||
"price": "7763.23",
|
||||
"amount": "55.95",
|
||||
"makerSide": "auction"
|
||||
},
|
||||
{
|
||||
"type": "auction_result",
|
||||
"eid": 2248795680,
|
||||
"result": "success",
|
||||
"time_ms": 1510866000000,
|
||||
"highest_bid_price": "7769",
|
||||
"lowest_ask_price": "7769.01",
|
||||
"collar_price": "7769.005",
|
||||
"auction_price": "7763.23",
|
||||
"auction_quantity": "55.95"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsMarketData(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"type": "update",
|
||||
"eventId": 5375461993,
|
||||
"socket_sequence": 0,
|
||||
"events": [
|
||||
{
|
||||
"type": "change",
|
||||
"reason": "initial",
|
||||
"price": "3641.61",
|
||||
"delta": "0.83372051",
|
||||
"remaining": "0.83372051",
|
||||
"side": "bid"
|
||||
},
|
||||
{
|
||||
"type": "change",
|
||||
"reason": "initial",
|
||||
"price": "3641.62",
|
||||
"delta": "4.072",
|
||||
"remaining": "4.072",
|
||||
"side": "ask"
|
||||
}
|
||||
]
|
||||
} `)
|
||||
err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "update",
|
||||
"eventId": 5375461993,
|
||||
"socket_sequence": 0,
|
||||
"events": [
|
||||
{
|
||||
"type": "change",
|
||||
"reason": "initial",
|
||||
"price": "3641.61",
|
||||
"delta": "0.83372051",
|
||||
"remaining": "0.83372051",
|
||||
"side": "bid"
|
||||
},
|
||||
{
|
||||
"type": "change",
|
||||
"reason": "initial",
|
||||
"price": "3641.62",
|
||||
"delta": "4.072",
|
||||
"remaining": "4.072",
|
||||
"side": "ask"
|
||||
}
|
||||
]
|
||||
} `)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"type": "update",
|
||||
"eventId": 5375503736,
|
||||
"timestamp": 1547759964,
|
||||
"timestampms": 1547759964051,
|
||||
"socket_sequence": 2,
|
||||
"events": [
|
||||
{
|
||||
"type": "change",
|
||||
"side": "bid",
|
||||
"price": "3628.01",
|
||||
"remaining": "0",
|
||||
"delta": "-2",
|
||||
"reason": "cancel"
|
||||
}
|
||||
]
|
||||
} `)
|
||||
err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseToStatus(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Status
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "accepted", Result: order.New},
|
||||
{Case: "booked", Result: order.Active},
|
||||
{Case: "fill", Result: order.Filled},
|
||||
{Case: "cancelled", Result: order.Cancelled},
|
||||
{Case: "cancel_rejected", Result: order.Rejected},
|
||||
{Case: "closed", Result: order.Filled},
|
||||
{Case: "LOL", Result: order.UnknownStatus},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderStatus(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseToOrderType(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Type
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "exchange limit", Result: order.Limit},
|
||||
{Case: "auction-only limit", Result: order.Limit},
|
||||
{Case: "indication-of-interest limit", Result: order.Limit},
|
||||
{Case: "market buy", Result: order.Market},
|
||||
{Case: "market sell", Result: order.Market},
|
||||
{Case: "block_trade", Result: order.Market},
|
||||
{Case: "LOL", Result: order.UnknownType},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderType(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,87 +275,29 @@ type WsHeartbeatResponse struct {
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
}
|
||||
|
||||
// WsActiveOrdersResponse contains active orders
|
||||
type WsActiveOrdersResponse struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
IsHidden bool `json:"is_hidden"`
|
||||
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
}
|
||||
|
||||
// WsOrderRejectedResponse ws response
|
||||
type WsOrderRejectedResponse struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
EventID string `json:"event_id"`
|
||||
Reason string `json:"reason"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
IsLive bool `json:"is_live"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
}
|
||||
|
||||
// WsOrderBookedResponse ws response
|
||||
type WsOrderBookedResponse struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
EventID string `json:"event_id"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
IsHidden bool `json:"is_hidden"`
|
||||
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
}
|
||||
|
||||
// WsOrderFilledResponse ws response
|
||||
type WsOrderFilledResponse struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
// WsOrderResponse contains active orders
|
||||
type WsOrderResponse struct {
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
IsHidden bool `json:"is_hidden"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
EventID string `json:"event_id"`
|
||||
CancelCommandID string `json:"cancel_command_id"`
|
||||
Reason string `json:"reason"`
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Fill WsOrderFilledData `json:"fill"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
}
|
||||
|
||||
// WsOrderFilledData ws response data
|
||||
@@ -368,72 +310,16 @@ type WsOrderFilledData struct {
|
||||
FeeCurrency string `json:"fee_currency"`
|
||||
}
|
||||
|
||||
// WsOrderCancelledResponse ws response
|
||||
type WsOrderCancelledResponse struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
EventID string `json:"event_id"`
|
||||
CancelCommandID string `json:"cancel_command_id,omitempty"`
|
||||
Reason string `json:"reason"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
IsHidden bool `json:"is_hidden"`
|
||||
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
type wsUnsubscribeResponse struct {
|
||||
Type string `json:"type"`
|
||||
Subscriptions []struct {
|
||||
Name string `json:"name"`
|
||||
Symbols []string `json:"symbols"`
|
||||
} `json:"subscriptions"`
|
||||
}
|
||||
|
||||
// WsOrderCancellationRejectedResponse ws response
|
||||
type WsOrderCancellationRejectedResponse struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
EventID string `json:"event_id"`
|
||||
CancelCommandID string `json:"cancel_command_id"`
|
||||
Reason string `json:"reason"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
IsHidden bool `json:"is_hidden"`
|
||||
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
}
|
||||
|
||||
// WsOrderClosedResponse ws response
|
||||
type WsOrderClosedResponse struct {
|
||||
Type string `json:"type"`
|
||||
OrderID string `json:"order_id"`
|
||||
EventID string `json:"event_id"`
|
||||
APISession string `json:"api_session"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"order_type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Timestampms int64 `json:"timestampms"`
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
IsHidden bool `json:"is_hidden"`
|
||||
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
SocketSequence int64 `json:"socket_sequence"`
|
||||
type wsCandleResponse struct {
|
||||
Type string `json:"type"`
|
||||
Symbol string `json:"symbol"`
|
||||
Changes [][]float64 `json:"changes"`
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (g *Gemini) WsConnect() error {
|
||||
dialer.Proxy = http.ProxyURL(proxy)
|
||||
}
|
||||
|
||||
go g.WsHandleData()
|
||||
go g.wsReadData()
|
||||
err := g.WsSecureSubscribe(&dialer, geminiWsOrderEvents)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", g.Name, err)
|
||||
@@ -82,7 +82,7 @@ func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error {
|
||||
return fmt.Errorf("%v Websocket connection %v error. Error %v",
|
||||
g.Name, endpoint, err)
|
||||
}
|
||||
go g.WsReadData(connection, enabledCurrencies[i])
|
||||
go g.wsFunnelConnectionData(connection, enabledCurrencies[i])
|
||||
if len(enabledCurrencies)-1 == i {
|
||||
return nil
|
||||
}
|
||||
@@ -126,13 +126,12 @@ func (g *Gemini) WsSecureSubscribe(dialer *websocket.Dialer, url string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v Websocket connection %v error. Error %v", g.Name, endpoint, err)
|
||||
}
|
||||
go g.WsReadData(g.AuthenticatedWebsocketConn, currency.Pair{})
|
||||
go g.wsFunnelConnectionData(g.AuthenticatedWebsocketConn, currency.Pair{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsReadData reads from the websocket connection and returns the websocket
|
||||
// response
|
||||
func (g *Gemini) WsReadData(ws *wshandler.WebsocketConnection, c currency.Pair) {
|
||||
// wsFunnelConnectionData receives data from multiple connections and passes it to wsReadData
|
||||
func (g *Gemini) wsFunnelConnectionData(ws *wshandler.WebsocketConnection, c currency.Pair) {
|
||||
g.Websocket.Wg.Add(1)
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
@@ -151,9 +150,8 @@ func (g *Gemini) WsReadData(ws *wshandler.WebsocketConnection, c currency.Pair)
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleData handles all the websocket data coming from the websocket
|
||||
// connection
|
||||
func (g *Gemini) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (g *Gemini) wsReadData() {
|
||||
g.Websocket.Wg.Add(1)
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
@@ -165,98 +163,199 @@ func (g *Gemini) WsHandleData() {
|
||||
if string(resp.Raw) == "[]" {
|
||||
continue
|
||||
}
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
err := g.wsHandleData(resp.Raw, resp.Currency)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v Error: %v, Raw: %v", g.Name, err, string(resp.Raw))
|
||||
continue
|
||||
}
|
||||
switch result["type"] {
|
||||
case "subscription_ack":
|
||||
var result WsSubscriptionAcknowledgementResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "initial":
|
||||
var result WsSubscriptionAcknowledgementResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "accepted":
|
||||
var result WsActiveOrdersResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "booked":
|
||||
var result WsOrderBookedResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "fill":
|
||||
var result WsOrderFilledResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "cancelled":
|
||||
var result WsOrderCancelledResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "closed":
|
||||
var result WsOrderClosedResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "heartbeat":
|
||||
var result WsHeartbeatResponse
|
||||
err := json.Unmarshal(resp.Raw, &result)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "update":
|
||||
if resp.Currency.IsEmpty() {
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v - unhandled data %s",
|
||||
g.Name, resp.Raw)
|
||||
continue
|
||||
}
|
||||
var marketUpdate WsMarketUpdateResponse
|
||||
err := json.Unmarshal(resp.Raw, &marketUpdate)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.wsProcessUpdate(marketUpdate, resp.Currency)
|
||||
default:
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v - unhandled data %s",
|
||||
g.Name, resp.Raw)
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gemini) wsHandleData(respRaw []byte, curr currency.Pair) error {
|
||||
// only order details are sent in arrays
|
||||
if strings.HasPrefix(string(respRaw), "[") {
|
||||
var result []WsOrderResponse
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range result {
|
||||
oSide, err := order.StringToOrderSide(result[i].Side)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: g.Name,
|
||||
OrderID: result[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oType order.Type
|
||||
oType, err = stringToOrderType(result[i].OrderType)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: g.Name,
|
||||
OrderID: result[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = stringToOrderStatus(result[i].Type)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: g.Name,
|
||||
OrderID: result[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
p := currency.NewPairFromString(result[i].Symbol)
|
||||
var a asset.Item
|
||||
a, err = g.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- &order.Detail{
|
||||
HiddenOrder: result[i].IsHidden,
|
||||
Price: result[i].Price,
|
||||
Amount: result[i].OriginalAmount,
|
||||
ExecutedAmount: result[i].ExecutedAmount,
|
||||
RemainingAmount: result[i].RemainingAmount,
|
||||
Exchange: g.Name,
|
||||
ID: result[i].OrderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: time.Unix(0, result[i].Timestampms*int64(time.Millisecond)),
|
||||
Pair: p,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v Error: %v, Raw: %v", g.Name, err, string(respRaw))
|
||||
}
|
||||
if _, ok := result["type"]; ok {
|
||||
switch result["type"] {
|
||||
case "subscription_ack":
|
||||
var result WsSubscriptionAcknowledgementResponse
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "unsubscribe":
|
||||
var result wsUnsubscribeResponse
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "initial":
|
||||
var result WsSubscriptionAcknowledgementResponse
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "heartbeat":
|
||||
var result WsHeartbeatResponse
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- result
|
||||
case "update":
|
||||
if curr.IsEmpty() {
|
||||
return fmt.Errorf("%v - `update` response error. Currency is empty %s",
|
||||
g.Name, respRaw)
|
||||
}
|
||||
var marketUpdate WsMarketUpdateResponse
|
||||
err := json.Unmarshal(respRaw, &marketUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.wsProcessUpdate(marketUpdate, curr)
|
||||
case "candles_1m_updates",
|
||||
"candles_5m_updates",
|
||||
"candles_15m_updates",
|
||||
"candles_30m_updates",
|
||||
"candles_1h_updates",
|
||||
"candles_6h_updates",
|
||||
"candles_1d_updates":
|
||||
var candle wsCandleResponse
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range candle.Changes {
|
||||
g.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(int64(candle.Changes[i][0])*1000, 0),
|
||||
Pair: curr,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
Interval: result["type"].(string),
|
||||
OpenPrice: candle.Changes[i][1],
|
||||
ClosePrice: candle.Changes[i][4],
|
||||
HighPrice: candle.Changes[i][2],
|
||||
LowPrice: candle.Changes[i][3],
|
||||
Volume: candle.Changes[i][5],
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
g.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: g.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
} else if _, ok := result["result"]; ok {
|
||||
switch result["result"].(string) {
|
||||
case "error":
|
||||
if _, ok := result["reason"]; ok {
|
||||
if _, ok := result["message"]; ok {
|
||||
return errors.New(result["reason"].(string) + " - " + result["message"].(string))
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%v Unhandled websocket error %s", g.Name, respRaw)
|
||||
default:
|
||||
g.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: g.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringToOrderStatus(status string) (order.Status, error) {
|
||||
switch status {
|
||||
case "accepted":
|
||||
return order.New, nil
|
||||
case "booked":
|
||||
return order.Active, nil
|
||||
case "fill":
|
||||
return order.Filled, nil
|
||||
case "cancelled":
|
||||
return order.Cancelled, nil
|
||||
case "cancel_rejected":
|
||||
return order.Rejected, nil
|
||||
case "closed":
|
||||
return order.Filled, nil
|
||||
default:
|
||||
return order.UnknownStatus, errors.New(status + " not recognised as order status")
|
||||
}
|
||||
}
|
||||
|
||||
func stringToOrderType(oType string) (order.Type, error) {
|
||||
switch oType {
|
||||
case "exchange limit", "auction-only limit", "indication-of-interest limit":
|
||||
return order.Limit, nil
|
||||
case "market buy", "market sell", "block_trade":
|
||||
// block trades are conducted off order-book, so their type is market, but would be considered a hidden trade
|
||||
return order.Market, nil
|
||||
default:
|
||||
return order.UnknownType, errors.New(oType + " not recognised as order type")
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessUpdate handles order book data
|
||||
func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pair) {
|
||||
if result.Timestamp == 0 && result.TimestampMS == 0 {
|
||||
@@ -295,7 +394,15 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa
|
||||
} else {
|
||||
var asks, bids []orderbook.Item
|
||||
for i := range result.Events {
|
||||
if result.Events[i].Type == "trade" {
|
||||
switch result.Events[i].Type {
|
||||
case "trade":
|
||||
tSide, err := order.StringToOrderSide(result.Events[i].MakerSide)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: g.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
g.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(0, result.Timestamp),
|
||||
CurrencyPair: pair,
|
||||
@@ -303,9 +410,9 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa
|
||||
Exchange: g.Name,
|
||||
Price: result.Events[i].Price,
|
||||
Amount: result.Events[i].Amount,
|
||||
Side: result.Events[i].MakerSide,
|
||||
Side: tSide,
|
||||
}
|
||||
} else {
|
||||
case "change":
|
||||
item := orderbook.Item{
|
||||
Amount: result.Events[i].Remaining,
|
||||
Price: result.Events[i].Price,
|
||||
@@ -315,8 +422,13 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa
|
||||
} else {
|
||||
bids = append(bids, item)
|
||||
}
|
||||
default:
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%s - Unhandled websocket update: %+v", g.Name, result)
|
||||
}
|
||||
}
|
||||
if len(asks) == 0 && len(bids) == 0 {
|
||||
return
|
||||
}
|
||||
err := g.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
|
||||
@@ -94,6 +94,9 @@ func (g *Gemini) SetDefaults() {
|
||||
TradeFetching: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
MessageSequenceNumbers: true,
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
KlineFetching: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission |
|
||||
exchange.AutoWithdrawCryptoWithSetup |
|
||||
@@ -338,7 +341,7 @@ func (g *Gemini) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
if s.OrderType != order.Limit {
|
||||
if s.Type != order.Limit {
|
||||
return submitOrderResponse,
|
||||
errors.New("only limit orders are enabled through this exchange")
|
||||
}
|
||||
@@ -347,7 +350,7 @@ func (g *Gemini) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
g.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
s.Amount,
|
||||
s.Price,
|
||||
s.OrderSide.String(),
|
||||
s.Side.String(),
|
||||
"exchange limit")
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
@@ -369,7 +372,7 @@ func (g *Gemini) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (g *Gemini) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -479,31 +482,31 @@ func (g *Gemini) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
ExecutedAmount: resp[i].ExecutedAmount,
|
||||
Exchange: g.Name,
|
||||
OrderType: orderType,
|
||||
OrderSide: side,
|
||||
Type: orderType,
|
||||
Side: side,
|
||||
Price: resp[i].Price,
|
||||
CurrencyPair: symbol,
|
||||
OrderDate: orderDate,
|
||||
Pair: symbol,
|
||||
Date: orderDate,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByType(&orders, req.OrderType)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("currency must be supplied")
|
||||
}
|
||||
|
||||
var trades []TradeHistory
|
||||
for j := range req.Currencies {
|
||||
resp, err := g.GetTradeHistory(g.FormatExchangeCurrency(req.Currencies[j],
|
||||
for j := range req.Pairs {
|
||||
resp, err := g.GetTradeHistory(g.FormatExchangeCurrency(req.Pairs[j],
|
||||
asset.Spot).String(),
|
||||
req.StartTicks.Unix())
|
||||
if err != nil {
|
||||
@@ -511,8 +514,8 @@ func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
}
|
||||
|
||||
for i := range resp {
|
||||
resp[i].BaseCurrency = req.Currencies[j].Base.String()
|
||||
resp[i].QuoteCurrency = req.Currencies[j].Quote.String()
|
||||
resp[i].BaseCurrency = req.Pairs[j].Base.String()
|
||||
resp[i].QuoteCurrency = req.Pairs[j].Quote.String()
|
||||
trades = append(trades, resp[i])
|
||||
}
|
||||
}
|
||||
@@ -523,21 +526,21 @@ func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
orderDate := time.Unix(trades[i].Timestamp, 0)
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: trades[i].Amount,
|
||||
ID: strconv.FormatInt(trades[i].OrderID, 10),
|
||||
Exchange: g.Name,
|
||||
OrderDate: orderDate,
|
||||
OrderSide: side,
|
||||
Fee: trades[i].FeeAmount,
|
||||
Price: trades[i].Price,
|
||||
CurrencyPair: currency.NewPairWithDelimiter(trades[i].BaseCurrency,
|
||||
Amount: trades[i].Amount,
|
||||
ID: strconv.FormatInt(trades[i].OrderID, 10),
|
||||
Exchange: g.Name,
|
||||
Date: orderDate,
|
||||
Side: side,
|
||||
Fee: trades[i].FeeAmount,
|
||||
Price: trades[i].Price,
|
||||
Pair: currency.NewPairWithDelimiter(trades[i].BaseCurrency,
|
||||
trades[i].QuoteCurrency,
|
||||
g.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ func TestMain(m *testing.M) {
|
||||
log.Fatal("HitBTC setup error", err)
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -225,8 +227,8 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)},
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)},
|
||||
}
|
||||
|
||||
_, err := h.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -239,8 +241,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)},
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.ETH, currency.BTC)},
|
||||
}
|
||||
|
||||
_, err := h.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -267,11 +269,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.DGD,
|
||||
Quote: currency.BTC,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := h.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -288,10 +290,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := h.CancelOrder(orderCancellation)
|
||||
@@ -310,10 +312,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := h.CancelAllOrders(orderCancellation)
|
||||
@@ -421,7 +423,7 @@ func setupWsAuth(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go h.WsHandleData()
|
||||
go h.wsReadData()
|
||||
h.wsLogin()
|
||||
timer := time.NewTimer(time.Second)
|
||||
select {
|
||||
@@ -508,11 +510,411 @@ func TestWsGetSymbols(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsGetTradingBalance dials websocket, sends get trading balance request.
|
||||
func TestSsGetCurrencies(t *testing.T) {
|
||||
// TestWsGetCurrencies dials websocket, sends get trading balance request.
|
||||
func TestWsGetCurrencies(t *testing.T) {
|
||||
setupWsAuth(t)
|
||||
_, err := h.wsGetCurrencies(currency.BTC)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsGetActiveOrdersJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "activeOrders",
|
||||
"params": [
|
||||
{
|
||||
"id": "4345613661",
|
||||
"clientOrderId": "57d5525562c945448e3cbd559bd068c3",
|
||||
"symbol": "BTCUSD",
|
||||
"side": "sell",
|
||||
"status": "new",
|
||||
"type": "limit",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": "0.013",
|
||||
"price": "0.100000",
|
||||
"cumQuantity": "0.000",
|
||||
"postOnly": false,
|
||||
"createdAt": "2017-10-20T12:17:12.245Z",
|
||||
"updatedAt": "2017-10-20T12:17:12.245Z",
|
||||
"reportType": "status"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsGetCurrenciesJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"id": "ETH",
|
||||
"fullName": "Ethereum",
|
||||
"crypto": true,
|
||||
"payinEnabled": true,
|
||||
"payinPaymentId": false,
|
||||
"payinConfirmations": 2,
|
||||
"payoutEnabled": true,
|
||||
"payoutIsPaymentId": false,
|
||||
"transferEnabled": true,
|
||||
"delisted": false,
|
||||
"payoutFee": "0.001"
|
||||
},
|
||||
"id": 123
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsGetSymbolsJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"id": "ETHBTC",
|
||||
"baseCurrency": "ETH",
|
||||
"quoteCurrency": "BTC",
|
||||
"quantityIncrement": "0.001",
|
||||
"tickSize": "0.000001",
|
||||
"takeLiquidityRate": "0.001",
|
||||
"provideLiquidityRate": "-0.0001",
|
||||
"feeCurrency": "BTC"
|
||||
},
|
||||
"id": 123
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "ticker",
|
||||
"params": {
|
||||
"ask": "0.054464",
|
||||
"bid": "0.054463",
|
||||
"last": "0.054463",
|
||||
"open": "0.057133",
|
||||
"low": "0.053615",
|
||||
"high": "0.057559",
|
||||
"volume": "33068.346",
|
||||
"volumeQuote": "1832.687530809",
|
||||
"timestamp": "2017-10-19T15:45:44.941Z",
|
||||
"symbol": "BTCUSD"
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderbook(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "snapshotOrderbook",
|
||||
"params": {
|
||||
"ask": [
|
||||
{
|
||||
"price": "0.054588",
|
||||
"size": "0.245"
|
||||
},
|
||||
{
|
||||
"price": "0.054590",
|
||||
"size": "0.000"
|
||||
},
|
||||
{
|
||||
"price": "0.054591",
|
||||
"size": "2.784"
|
||||
}
|
||||
],
|
||||
"bid": [
|
||||
{
|
||||
"price": "0.054558",
|
||||
"size": "0.500"
|
||||
},
|
||||
{
|
||||
"price": "0.054557",
|
||||
"size": "0.076"
|
||||
},
|
||||
{
|
||||
"price": "0.054524",
|
||||
"size": "7.725"
|
||||
}
|
||||
],
|
||||
"symbol": "BTCUSD",
|
||||
"sequence": 8073827,
|
||||
"timestamp": "2018-11-19T05:00:28.193Z"
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "updateOrderbook",
|
||||
"params": {
|
||||
"ask": [
|
||||
{
|
||||
"price": "0.054590",
|
||||
"size": "0.000"
|
||||
},
|
||||
{
|
||||
"price": "0.054591",
|
||||
"size": "0.000"
|
||||
}
|
||||
],
|
||||
"bid": [
|
||||
{
|
||||
"price": "0.054504",
|
||||
"size": "0.000"
|
||||
}
|
||||
],
|
||||
"symbol": "BTCUSD",
|
||||
"sequence": 8073830,
|
||||
"timestamp": "2018-11-19T05:00:28.700Z"
|
||||
}
|
||||
}`)
|
||||
err = h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderNotification(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "report",
|
||||
"params": {
|
||||
"id": "4345697765",
|
||||
"clientOrderId": "53b7cf917963464a811a4af426102c19",
|
||||
"symbol": "BTCUSD",
|
||||
"side": "sell",
|
||||
"status": "filled",
|
||||
"type": "limit",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": "0.001",
|
||||
"price": "0.053868",
|
||||
"cumQuantity": "0.001",
|
||||
"postOnly": false,
|
||||
"createdAt": "2017-10-20T12:20:05.952Z",
|
||||
"updatedAt": "2017-10-20T12:20:38.708Z",
|
||||
"reportType": "trade",
|
||||
"tradeQuantity": "0.001",
|
||||
"tradePrice": "0.053868",
|
||||
"tradeId": 55051694,
|
||||
"tradeFee": "-0.000000005"
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSubmitOrderJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"id": "4345947689",
|
||||
"clientOrderId": "57d5525562c945448e3cbd559bd068c4",
|
||||
"symbol": "BTCUSD",
|
||||
"side": "sell",
|
||||
"status": "new",
|
||||
"type": "limit",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": "0.001",
|
||||
"price": "0.093837",
|
||||
"cumQuantity": "0.000",
|
||||
"postOnly": false,
|
||||
"createdAt": "2017-10-20T12:29:43.166Z",
|
||||
"updatedAt": "2017-10-20T12:29:43.166Z",
|
||||
"reportType": "new"
|
||||
},
|
||||
"id": 123
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsCancelOrderJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"id": "4345947689",
|
||||
"clientOrderId": "57d5525562c945448e3cbd559bd068c4",
|
||||
"symbol": "BTCUSD",
|
||||
"side": "sell",
|
||||
"status": "canceled",
|
||||
"type": "limit",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": "0.001",
|
||||
"price": "0.093837",
|
||||
"cumQuantity": "0.000",
|
||||
"postOnly": false,
|
||||
"createdAt": "2017-10-20T12:29:43.166Z",
|
||||
"updatedAt": "2017-10-20T12:31:26.174Z",
|
||||
"reportType": "canceled"
|
||||
},
|
||||
"id": 123
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsCancelReplaceJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"id": "4346371528",
|
||||
"clientOrderId": "9cbe79cb6f864b71a811402a48d4b5b2",
|
||||
"symbol": "BTCUSD",
|
||||
"side": "sell",
|
||||
"status": "new",
|
||||
"type": "limit",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": "0.002",
|
||||
"price": "0.083837",
|
||||
"cumQuantity": "0.000",
|
||||
"postOnly": false,
|
||||
"createdAt": "2017-10-20T12:47:07.942Z",
|
||||
"updatedAt": "2017-10-20T12:50:34.488Z",
|
||||
"reportType": "replaced",
|
||||
"originalRequestClientOrderId": "9cbe79cb6f864b71a811402a48d4b5b1"
|
||||
},
|
||||
"id": 123
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsGetTradesRequestResponse(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"result": [
|
||||
{
|
||||
"currency": "BCN",
|
||||
"available": "100.000000000",
|
||||
"reserved": "0"
|
||||
},
|
||||
{
|
||||
"currency": "BTC",
|
||||
"available": "0.013634021",
|
||||
"reserved": "0"
|
||||
},
|
||||
{
|
||||
"currency": "ETH",
|
||||
"available": "0",
|
||||
"reserved": "0.00200000"
|
||||
}
|
||||
],
|
||||
"id": 123
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsGetActiveOrdersRequestJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"result": [
|
||||
{
|
||||
"id": "4346371528",
|
||||
"clientOrderId": "9cbe79cb6f864b71a811402a48d4b5b2",
|
||||
"symbol": "BTCUSD",
|
||||
"side": "sell",
|
||||
"status": "new",
|
||||
"type": "limit",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": "0.002",
|
||||
"price": "0.083837",
|
||||
"cumQuantity": "0.000",
|
||||
"postOnly": false,
|
||||
"createdAt": "2017-10-20T12:47:07.942Z",
|
||||
"updatedAt": "2017-10-20T12:50:34.488Z",
|
||||
"reportType": "replaced",
|
||||
"originalRequestClientOrderId": "9cbe79cb6f864b71a811402a48d4b5b1"
|
||||
}
|
||||
],
|
||||
"id": 123
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrades(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "snapshotTrades",
|
||||
"params": {
|
||||
"data": [
|
||||
{
|
||||
"id": 54469456,
|
||||
"price": "0.054656",
|
||||
"quantity": "0.057",
|
||||
"side": "buy",
|
||||
"timestamp": "2017-10-19T16:33:42.821Z"
|
||||
},
|
||||
{
|
||||
"id": 54469497,
|
||||
"price": "0.054656",
|
||||
"quantity": "0.092",
|
||||
"side": "buy",
|
||||
"timestamp": "2017-10-19T16:33:48.754Z"
|
||||
},
|
||||
{
|
||||
"id": 54469697,
|
||||
"price": "0.054669",
|
||||
"quantity": "0.002",
|
||||
"side": "buy",
|
||||
"timestamp": "2017-10-19T16:34:13.288Z"
|
||||
}
|
||||
],
|
||||
"symbol": "BTCUSD"
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "updateTrades",
|
||||
"params": {
|
||||
"data": [
|
||||
{
|
||||
"id": 54469813,
|
||||
"price": "0.054670",
|
||||
"quantity": "0.183",
|
||||
"side": "buy",
|
||||
"timestamp": "2017-10-19T16:34:25.041Z"
|
||||
}
|
||||
],
|
||||
"symbol": "BTCUSD"
|
||||
}
|
||||
} `)
|
||||
err = h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +52,11 @@ type Orderbook struct {
|
||||
|
||||
// TradeHistory contains trade history data
|
||||
type TradeHistory struct {
|
||||
ID int64 `json:"id"` // Trade id
|
||||
Timestamp string `json:"timestamp"` // Trade timestamp
|
||||
Side string `json:"side"` // Trade side sell or buy
|
||||
Price float64 `json:"price,string"` // Trade price
|
||||
Quantity float64 `json:"quantity,string"` // Trade quantity
|
||||
ID int64 `json:"id"` // Trade id
|
||||
Timestamp time.Time `json:"timestamp"` // Trade timestamp
|
||||
Side string `json:"side"` // Trade side sell or buy
|
||||
Price float64 `json:"price,string"` // Trade price
|
||||
Quantity float64 `json:"quantity,string"` // Trade quantity
|
||||
}
|
||||
|
||||
// ChartData contains chart data
|
||||
@@ -322,16 +322,16 @@ type params struct {
|
||||
// WsTicker defines websocket ticker feed return params
|
||||
type WsTicker struct {
|
||||
Params struct {
|
||||
Ask float64 `json:"ask,string"`
|
||||
Bid float64 `json:"bid,string"`
|
||||
Last float64 `json:"last,string"`
|
||||
Open float64 `json:"open,string"`
|
||||
Low float64 `json:"low,string"`
|
||||
High float64 `json:"high,string"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
VolumeQuote float64 `json:"volumeQuote,string"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Symbol string `json:"symbol"`
|
||||
Ask float64 `json:"ask,string"`
|
||||
Bid float64 `json:"bid,string"`
|
||||
Last float64 `json:"last,string"`
|
||||
Open float64 `json:"open,string"`
|
||||
Low float64 `json:"low,string"`
|
||||
High float64 `json:"high,string"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
VolumeQuote float64 `json:"volumeQuote,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Symbol string `json:"symbol"`
|
||||
} `json:"params"`
|
||||
}
|
||||
|
||||
@@ -355,11 +355,11 @@ type WsOrderbook struct {
|
||||
type WsTrade struct {
|
||||
Params struct {
|
||||
Data []struct {
|
||||
ID int64 `json:"id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
Side string `json:"side"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
ID int64 `json:"id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
Side string `json:"side"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
} `json:"data"`
|
||||
Symbol string `json:"symbol"`
|
||||
} `json:"params"`
|
||||
@@ -379,28 +379,48 @@ type WsLoginData struct {
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// WsActiveOrdersResponse Active order response for auth subscription to reports
|
||||
type WsActiveOrdersResponse struct {
|
||||
Params []WsActiveOrdersResponseData `json:"params"`
|
||||
Error ResponseError `json:"error,omitempty"`
|
||||
// wsActiveOrdersResponse Active order response for auth subscription to reports
|
||||
type wsActiveOrdersResponse struct {
|
||||
Params []wsOrderData `json:"params"`
|
||||
Error ResponseError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// WsActiveOrdersResponseData Active order data for WsActiveOrdersResponse
|
||||
type WsActiveOrdersResponseData struct {
|
||||
ID string `json:"id"`
|
||||
ClientOrderID string `json:"clientOrderId,omitempty"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
CumQuantity float64 `json:"cumQuantity,string"`
|
||||
PostOnly bool `json:"postOnly"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ReportType string `json:"reportType"`
|
||||
type wsReportResponse struct {
|
||||
OrderData wsOrderData `json:"params"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type wsOrderResponse struct {
|
||||
OrderData wsOrderData `json:"result"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type wsActiveOrderRequestResponse struct {
|
||||
OrderData []wsOrderData `json:"result"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
// wsOrderData Active order data for WsActiveOrdersResponse
|
||||
type wsOrderData struct {
|
||||
ID string `json:"id"`
|
||||
ClientOrderID string `json:"clientOrderId,omitempty"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
TimeInForce string `json:"timeInForce"`
|
||||
Quantity float64 `json:"quantity,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
CumQuantity float64 `json:"cumQuantity,string"`
|
||||
PostOnly bool `json:"postOnly"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ReportType string `json:"reportType"`
|
||||
OriginalRequestClientOrderID string `json:"originalRequestClientOrderId"`
|
||||
TradeQuantity float64 `json:"tradeQuantity,string"`
|
||||
TradePrice float64 `json:"tradePrice,string"`
|
||||
TradeID float64 `json:"tradeId"`
|
||||
TradeFee float64 `json:"tradeFee,string"`
|
||||
}
|
||||
|
||||
// WsReportResponse report response for auth subscription to reports
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/nonce"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -26,6 +27,7 @@ const (
|
||||
hitbtcWebsocketAddress = "wss://api.hitbtc.com/api/2/ws"
|
||||
rpcVersion = "2.0"
|
||||
rateLimit = 20
|
||||
errAuthFailed = 1002
|
||||
)
|
||||
|
||||
var requestID nonce.Nonce
|
||||
@@ -40,7 +42,7 @@ func (h *HitBTC) WsConnect() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go h.WsHandleData()
|
||||
go h.wsReadData()
|
||||
err = h.wsLogin()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err)
|
||||
@@ -51,8 +53,8 @@ func (h *HitBTC) WsConnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles websocket data
|
||||
func (h *HitBTC) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (h *HitBTC) wsReadData() {
|
||||
h.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
@@ -63,7 +65,6 @@ func (h *HitBTC) WsHandleData() {
|
||||
select {
|
||||
case <-h.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
default:
|
||||
resp, err := h.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
@@ -72,50 +73,82 @@ func (h *HitBTC) WsHandleData() {
|
||||
}
|
||||
h.Websocket.TrafficAlert <- struct{}{}
|
||||
|
||||
var init capture
|
||||
err = json.Unmarshal(resp.Raw, &init)
|
||||
err = h.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
if init.Error.Code == 1002 {
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
if init.ID > 0 {
|
||||
h.WebsocketConn.AddResponseWithID(init.ID, resp.Raw)
|
||||
continue
|
||||
}
|
||||
if init.Error.Message != "" || init.Error.Code != 0 {
|
||||
h.Websocket.DataHandler <- fmt.Errorf("hitbtc.go error - Code: %d, Message: %s",
|
||||
init.Error.Code,
|
||||
init.Error.Message)
|
||||
continue
|
||||
}
|
||||
if _, ok := init.Result.(bool); ok {
|
||||
continue
|
||||
}
|
||||
if init.Method != "" {
|
||||
h.handleSubscriptionUpdates(resp, init)
|
||||
} else {
|
||||
h.handleCommandResponses(resp, init)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, init capture) {
|
||||
switch init.Method {
|
||||
func (h *HitBTC) wsGetTableName(respRaw []byte) (string, error) {
|
||||
var init capture
|
||||
err := json.Unmarshal(respRaw, &init)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if init.Error.Code == errAuthFailed {
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
if init.ID > 0 {
|
||||
if h.WebsocketConn.IsIDWaitingForResponse(init.ID) {
|
||||
h.WebsocketConn.SetResponseIDAndData(init.ID, respRaw)
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if init.Error.Message != "" || init.Error.Code != 0 {
|
||||
return "", fmt.Errorf("hitbtc.go error - Code: %d, Message: %s",
|
||||
init.Error.Code,
|
||||
init.Error.Message)
|
||||
}
|
||||
if _, ok := init.Result.(bool); ok {
|
||||
return "", nil
|
||||
}
|
||||
if init.Method != "" {
|
||||
return init.Method, nil
|
||||
}
|
||||
switch resultType := init.Result.(type) {
|
||||
case map[string]interface{}:
|
||||
if reportType, ok := resultType["reportType"].(string); ok {
|
||||
return reportType, nil
|
||||
}
|
||||
// check for ids - means it was a specific request
|
||||
// and can't go through normal processing
|
||||
if responseID, ok := resultType["id"].(string); ok {
|
||||
if responseID != "" {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
if len(resultType) == 0 {
|
||||
h.Websocket.DataHandler <- fmt.Sprintf("No data returned. ID: %v", init.ID)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
data := resultType[0].(map[string]interface{})
|
||||
if _, ok := data["clientOrderId"]; ok {
|
||||
return "order", nil
|
||||
} else if _, ok := data["available"]; ok {
|
||||
return "trading", nil
|
||||
}
|
||||
}
|
||||
h.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: h.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (h *HitBTC) wsHandleData(respRaw []byte) error {
|
||||
name, err := h.wsGetTableName(respRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch name {
|
||||
case "":
|
||||
return nil
|
||||
case "ticker":
|
||||
var wsTicker WsTicker
|
||||
err := json.Unmarshal(resp.Raw, &wsTicker)
|
||||
err := json.Unmarshal(respRaw, &wsTicker)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
ts, err := time.Parse(time.RFC3339, wsTicker.Params.Timestamp)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: h.Name,
|
||||
@@ -127,105 +160,102 @@ func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, ini
|
||||
Bid: wsTicker.Params.Bid,
|
||||
Ask: wsTicker.Params.Ask,
|
||||
Last: wsTicker.Params.Last,
|
||||
LastUpdated: ts,
|
||||
LastUpdated: wsTicker.Params.Timestamp,
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromFormattedPairs(wsTicker.Params.Symbol,
|
||||
h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)),
|
||||
}
|
||||
case "snapshotOrderbook":
|
||||
var obSnapshot WsOrderbook
|
||||
err := json.Unmarshal(resp.Raw, &obSnapshot)
|
||||
err := json.Unmarshal(respRaw, &obSnapshot)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
err = h.WsProcessOrderbookSnapshot(obSnapshot)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
case "updateOrderbook":
|
||||
var obUpdate WsOrderbook
|
||||
err := json.Unmarshal(resp.Raw, &obUpdate)
|
||||
err := json.Unmarshal(respRaw, &obUpdate)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
err = h.WsProcessOrderbookUpdate(obUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.WsProcessOrderbookUpdate(obUpdate)
|
||||
case "snapshotTrades":
|
||||
var tradeSnapshot WsTrade
|
||||
err := json.Unmarshal(resp.Raw, &tradeSnapshot)
|
||||
err := json.Unmarshal(respRaw, &tradeSnapshot)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
case "updateTrades":
|
||||
var tradeUpdates WsTrade
|
||||
err := json.Unmarshal(resp.Raw, &tradeUpdates)
|
||||
err := json.Unmarshal(respRaw, &tradeUpdates)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
case "activeOrders":
|
||||
var activeOrders WsActiveOrdersResponse
|
||||
err := json.Unmarshal(resp.Raw, &activeOrders)
|
||||
var o wsActiveOrdersResponse
|
||||
err := json.Unmarshal(respRaw, &o)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- activeOrders
|
||||
for i := range o.Params {
|
||||
err = h.wsHandleOrderData(&o.Params[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case "trading":
|
||||
var trades WsGetTradingBalanceResponse
|
||||
err := json.Unmarshal(respRaw, &trades)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- trades
|
||||
case "report":
|
||||
var reportData WsReportResponse
|
||||
err := json.Unmarshal(resp.Raw, &reportData)
|
||||
var o wsReportResponse
|
||||
err := json.Unmarshal(respRaw, &o)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- reportData
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HitBTC) handleCommandResponses(resp wshandler.WebsocketResponse, init capture) {
|
||||
switch resultType := init.Result.(type) {
|
||||
case map[string]interface{}:
|
||||
switch resultType["reportType"].(string) {
|
||||
case "new":
|
||||
var response WsSubmitOrderSuccessResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
case "canceled":
|
||||
var response WsCancelOrderResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
case "replaced":
|
||||
var response WsReplaceOrderResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
}
|
||||
case []interface{}:
|
||||
if len(resultType) == 0 {
|
||||
h.Websocket.DataHandler <- fmt.Sprintf("No data returned. ID: %v", init.ID)
|
||||
return
|
||||
}
|
||||
data := resultType[0].(map[string]interface{})
|
||||
if _, ok := data["clientOrderId"]; ok {
|
||||
var response WsActiveOrdersResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
} else if _, ok := data["available"]; ok {
|
||||
var response WsGetTradingBalanceResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
return err
|
||||
}
|
||||
err = h.wsHandleOrderData(&o.OrderData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "order":
|
||||
var o wsActiveOrderRequestResponse
|
||||
err := json.Unmarshal(respRaw, &o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range o.OrderData {
|
||||
err = h.wsHandleOrderData(&o.OrderData[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case
|
||||
"replaced",
|
||||
"canceled",
|
||||
"new":
|
||||
var o wsOrderResponse
|
||||
err := json.Unmarshal(respRaw, &o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = h.wsHandleOrderData(&o.OrderData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
h.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: h.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsProcessOrderbookSnapshot processes a full orderbook snapshot to a local cache
|
||||
@@ -269,6 +299,68 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HitBTC) wsHandleOrderData(o *wsOrderData) error {
|
||||
var trades []order.TradeHistory
|
||||
if o.TradeID > 0 {
|
||||
trades = append(trades, order.TradeHistory{
|
||||
Price: o.TradePrice,
|
||||
Amount: o.TradeQuantity,
|
||||
Fee: o.TradeFee,
|
||||
Exchange: h.Name,
|
||||
TID: strconv.FormatFloat(o.TradeID, 'f', -1, 64),
|
||||
Timestamp: o.UpdatedAt,
|
||||
})
|
||||
}
|
||||
oType, err := order.StringToOrderType(o.Type)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: o.ID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
o.Status = strings.Replace(o.Status, "canceled", "cancelled", 1)
|
||||
oStatus, err := order.StringToOrderStatus(o.Status)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: o.ID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oSide, err := order.StringToOrderSide(o.Side)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: o.ID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
p := currency.NewPairFromString(o.Symbol)
|
||||
var a asset.Item
|
||||
a, err = h.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- &order.Detail{
|
||||
Price: o.Price,
|
||||
Amount: o.Quantity,
|
||||
ExecutedAmount: o.CumQuantity,
|
||||
RemainingAmount: o.Quantity - o.CumQuantity,
|
||||
Exchange: h.Name,
|
||||
ID: o.ID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: o.CreatedAt,
|
||||
LastUpdated: o.UpdatedAt,
|
||||
Pair: p,
|
||||
Trades: trades,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsProcessOrderbookUpdate updates a local cache
|
||||
func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error {
|
||||
if len(update.Params.Bid) == 0 && len(update.Params.Ask) == 0 {
|
||||
@@ -397,14 +489,14 @@ func (h *HitBTC) wsLogin() error {
|
||||
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name)
|
||||
}
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
nonce := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(nonce), []byte(h.API.Credentials.Secret))
|
||||
n := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(n), []byte(h.API.Credentials.Secret))
|
||||
request := WsLoginRequest{
|
||||
Method: "login",
|
||||
Params: WsLoginData{
|
||||
Algo: "HS256",
|
||||
PKey: h.API.Credentials.Key,
|
||||
Nonce: nonce,
|
||||
Nonce: n,
|
||||
Signature: crypto.HexEncodeToString(hmac),
|
||||
},
|
||||
}
|
||||
@@ -507,7 +599,7 @@ func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) (
|
||||
}
|
||||
|
||||
// wsGetActiveOrders sends a websocket message to get all active orders
|
||||
func (h *HitBTC) wsGetActiveOrders() (*WsActiveOrdersResponse, error) {
|
||||
func (h *HitBTC) wsGetActiveOrders() (*wsActiveOrdersResponse, error) {
|
||||
if !h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
|
||||
}
|
||||
@@ -520,7 +612,7 @@ func (h *HitBTC) wsGetActiveOrders() (*WsActiveOrdersResponse, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v", h.Name, err)
|
||||
}
|
||||
var response WsActiveOrdersResponse
|
||||
var response wsActiveOrdersResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v", h.Name, err)
|
||||
|
||||
@@ -102,6 +102,8 @@ func (h *HitBTC) SetDefaults() {
|
||||
SubmitOrder: true,
|
||||
CancelOrder: true,
|
||||
MessageSequenceNumbers: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
@@ -396,7 +398,7 @@ func (h *HitBTC) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
if h.Websocket.IsConnected() && h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
var response *WsSubmitOrderSuccessResponse
|
||||
response, err = h.wsPlaceOrder(o.Pair, o.OrderSide.String(), o.Amount, o.Price)
|
||||
response, err = h.wsPlaceOrder(o.Pair, o.Side.String(), o.Amount, o.Price)
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
@@ -409,15 +411,15 @@ func (h *HitBTC) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
response, err = h.PlaceOrder(o.Pair.String(),
|
||||
o.Price,
|
||||
o.Amount,
|
||||
strings.ToLower(o.OrderType.String()),
|
||||
strings.ToLower(o.OrderSide.String()))
|
||||
strings.ToLower(o.Type.String()),
|
||||
strings.ToLower(o.Side.String()))
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
if response.OrderNumber > 0 {
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(response.OrderNumber, 10)
|
||||
}
|
||||
if o.OrderType == order.Market {
|
||||
if o.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
}
|
||||
@@ -434,7 +436,7 @@ func (h *HitBTC) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (h *HitBTC) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -522,13 +524,13 @@ func (h *HitBTC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error)
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (h *HitBTC) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("currency must be supplied")
|
||||
}
|
||||
|
||||
var allOrders []OrderHistoryResponse
|
||||
for i := range req.Currencies {
|
||||
resp, err := h.GetOpenOrders(req.Currencies[i].String())
|
||||
for i := range req.Pairs {
|
||||
resp, err := h.GetOpenOrders(req.Pairs[i].String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -541,31 +543,31 @@ func (h *HitBTC) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
h.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
side := order.Side(strings.ToUpper(allOrders[i].Side))
|
||||
orders = append(orders, order.Detail{
|
||||
ID: allOrders[i].ID,
|
||||
Amount: allOrders[i].Quantity,
|
||||
Exchange: h.Name,
|
||||
Price: allOrders[i].Price,
|
||||
OrderDate: allOrders[i].CreatedAt,
|
||||
OrderSide: side,
|
||||
CurrencyPair: symbol,
|
||||
ID: allOrders[i].ID,
|
||||
Amount: allOrders[i].Quantity,
|
||||
Exchange: h.Name,
|
||||
Price: allOrders[i].Price,
|
||||
Date: allOrders[i].CreatedAt,
|
||||
Side: side,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (h *HitBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("currency must be supplied")
|
||||
}
|
||||
|
||||
var allOrders []OrderHistoryResponse
|
||||
for i := range req.Currencies {
|
||||
resp, err := h.GetOrders(req.Currencies[i].String())
|
||||
for i := range req.Pairs {
|
||||
resp, err := h.GetOrders(req.Pairs[i].String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -578,18 +580,18 @@ func (h *HitBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
h.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
side := order.Side(strings.ToUpper(allOrders[i].Side))
|
||||
orders = append(orders, order.Detail{
|
||||
ID: allOrders[i].ID,
|
||||
Amount: allOrders[i].Quantity,
|
||||
Exchange: h.Name,
|
||||
Price: allOrders[i].Price,
|
||||
OrderDate: allOrders[i].CreatedAt,
|
||||
OrderSide: side,
|
||||
CurrencyPair: symbol,
|
||||
ID: allOrders[i].ID,
|
||||
Amount: allOrders[i].Quantity,
|
||||
Exchange: h.Name,
|
||||
Price: allOrders[i].Price,
|
||||
Date: allOrders[i].CreatedAt,
|
||||
Side: side,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("Huobi setup error", err)
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ func setupWsTests(t *testing.T) {
|
||||
comms = make(chan WsMessage, sharedtestvalues.WebsocketChannelOverrideCapacity)
|
||||
h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go h.WsHandleData()
|
||||
go h.wsReadData()
|
||||
h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: h.Name,
|
||||
URL: wsAccountsOrdersURL,
|
||||
@@ -419,8 +420,8 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)},
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)},
|
||||
}
|
||||
|
||||
_, err := h.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -433,8 +434,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)},
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)},
|
||||
}
|
||||
|
||||
_, err := h.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -470,11 +471,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USDT,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: strconv.FormatInt(accounts[0].ID, 10),
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: strconv.FormatInt(accounts[0].ID, 10),
|
||||
}
|
||||
response, err := h.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -489,10 +490,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := h.CancelOrder(orderCancellation)
|
||||
@@ -512,10 +513,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := h.CancelAllOrders(&orderCancellation)
|
||||
@@ -655,7 +656,379 @@ func TestWsGetOrderDetails(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.ErrorCode > 0 && (orderID == "123" && resp.ErrorCode != 10022) {
|
||||
if resp.ErrorCode > 0 && resp.ErrorCode != 10022 {
|
||||
t.Error(resp.ErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSubResponse(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"op": "sub",
|
||||
"cid": "123",
|
||||
"err-code": 0,
|
||||
"ts": 1489474081631,
|
||||
"topic": "accounts"
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsKline(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"ch": "market.btcusdt.kline.1min",
|
||||
"ts": 1489474082831,
|
||||
"tick": {
|
||||
"id": 1489464480,
|
||||
"amount": 0.0,
|
||||
"count": 0,
|
||||
"open": 7962.62,
|
||||
"close": 7962.62,
|
||||
"low": 7962.62,
|
||||
"high": 7962.62,
|
||||
"vol": 0.0
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsUnsubscribe(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"id": "id4",
|
||||
"status": "ok",
|
||||
"unsubbed": "market.btcusdt.trade.detail",
|
||||
"ts": 1494326028889
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsKlineArray(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"status": "ok",
|
||||
"rep": "market.btcusdt.kline.1min",
|
||||
"data": [
|
||||
{
|
||||
"amount": 1.6206,
|
||||
"count": 3,
|
||||
"id": 1494465840,
|
||||
"open": 9887.00,
|
||||
"close": 9885.00,
|
||||
"low": 9885.00,
|
||||
"high": 9887.00,
|
||||
"vol": 16021.632026
|
||||
},
|
||||
{
|
||||
"amount": 2.2124,
|
||||
"count": 6,
|
||||
"id": 1494465900,
|
||||
"open": 9885.00,
|
||||
"close": 9880.00,
|
||||
"low": 9880.00,
|
||||
"high": 9885.00,
|
||||
"vol": 21859.023500
|
||||
}
|
||||
]
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsMarketDepth(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"ch": "market.htusdt.depth.step0",
|
||||
"ts": 1572362902027,
|
||||
"tick": {
|
||||
"bids": [
|
||||
[3.7721, 344.86],
|
||||
[3.7709, 46.66]
|
||||
],
|
||||
"asks": [
|
||||
[3.7745, 15.44],
|
||||
[3.7746, 70.52]
|
||||
],
|
||||
"version": 100434317651,
|
||||
"ts": 1572362902012
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsBestBidOffer(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"ch": "market.btcusdt.bbo",
|
||||
"ts": 1489474082831,
|
||||
"tick": {
|
||||
"symbol": "btcusdt",
|
||||
"quoteTime": "1489474082811",
|
||||
"bid": "10008.31",
|
||||
"bidSize": "0.01",
|
||||
"ask": "10009.54",
|
||||
"askSize": "0.3"
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTradeDetail(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"ch": "market.btcusdt.trade.detail",
|
||||
"ts": 1489474082831,
|
||||
"tick": {
|
||||
"id": 14650745135,
|
||||
"ts": 1533265950234,
|
||||
"data": [
|
||||
{
|
||||
"amount": 0.0099,
|
||||
"ts": 1533265950234,
|
||||
"id": 146507451359183894799,
|
||||
"tradeId": 102043495674,
|
||||
"price": 401.74,
|
||||
"direction": "buy"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"rep": "market.btcusdt.detail",
|
||||
"id": "id11",
|
||||
"data":{
|
||||
"amount": 12224.2922,
|
||||
"open": 9790.52,
|
||||
"close": 10195.00,
|
||||
"high": 10300.00,
|
||||
"ts": 1494496390000,
|
||||
"id": 1494496390,
|
||||
"count": 15195,
|
||||
"low": 9657.00,
|
||||
"vol": 121906001.754751
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsAccountUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"op": "notify",
|
||||
"ts": 1522856623232,
|
||||
"topic": "accounts",
|
||||
"data": {
|
||||
"event": "order.place",
|
||||
"list": [
|
||||
{
|
||||
"account-id": 419013,
|
||||
"currency": "usdt",
|
||||
"type": "trade",
|
||||
"balance": "500009195917.4362872650"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrderUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"op": "notify",
|
||||
"topic": "orders.htusdt",
|
||||
"ts": 1522856623232,
|
||||
"data": {
|
||||
"seq-id": 94984,
|
||||
"order-id": 2039498445,
|
||||
"symbol": "btcusdt",
|
||||
"account-id": 100077,
|
||||
"order-amount": "5000.000000000000000000",
|
||||
"order-price": "1.662100000000000000",
|
||||
"created-at": 1522858623622,
|
||||
"order-type": "buy-limit",
|
||||
"order-source": "api",
|
||||
"order-state": "filled",
|
||||
"role": "taker",
|
||||
"price": "1.662100000000000000",
|
||||
"filled-amount": "5000.000000000000000000",
|
||||
"unfilled-amount": "0.000000000000000000",
|
||||
"filled-cash-amount": "8301.357280000000000000",
|
||||
"filled-fees": "8.000000000000000000"
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSubsbOp(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"op": "unsub",
|
||||
"topic": "accounts",
|
||||
"cid": "123"
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`{
|
||||
"op": "sub",
|
||||
"cid": "123",
|
||||
"err-code": 0,
|
||||
"ts": 1489474081631,
|
||||
"topic": "accounts"
|
||||
}`)
|
||||
err = h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsMarketByPrice(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"ch": "market.btcusdt.mbp.150",
|
||||
"ts": 1573199608679,
|
||||
"tick": {
|
||||
"seqNum": 100020146795,
|
||||
"prevSeqNum": 100020146794,
|
||||
"bids": [],
|
||||
"asks": [
|
||||
[645.140000000000000000, 26.755973959140651643]
|
||||
]
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`{
|
||||
"id": "id2",
|
||||
"rep": "market.btcusdt.mbp.150",
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"seqNum": 100020142010,
|
||||
"bids": [
|
||||
[618.37, 71.594],
|
||||
[423.33, 77.726],
|
||||
[223.18, 47.997],
|
||||
[219.34, 24.82],
|
||||
[210.34, 94.463]
|
||||
],
|
||||
"asks": [
|
||||
[650.59, 14.909733438479636],
|
||||
[650.63, 97.996],
|
||||
[650.77, 97.465],
|
||||
[651.23, 83.973],
|
||||
[651.42, 34.465]
|
||||
]
|
||||
}
|
||||
}`)
|
||||
err = h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrdersUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"op": "notify",
|
||||
"ts": 1522856623232,
|
||||
"topic": "orders.btcusdt.update",
|
||||
"data": {
|
||||
"unfilled-amount": "0.000000000000000000",
|
||||
"filled-amount": "5000.000000000000000000",
|
||||
"price": "1.662100000000000000",
|
||||
"order-id": 2039498445,
|
||||
"symbol": "btcusdt",
|
||||
"match-id": 94984,
|
||||
"filled-cash-amount": "8301.357280000000000000",
|
||||
"role": "taker|maker",
|
||||
"order-state": "filled",
|
||||
"client-order-id": "a0001",
|
||||
"order-type": "buy-limit"
|
||||
}
|
||||
}`)
|
||||
err := h.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToOrderStatus(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Status
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "submitted", Result: order.New},
|
||||
{Case: "canceled", Result: order.Cancelled},
|
||||
{Case: "partial-filled", Result: order.PartiallyFilled},
|
||||
{Case: "partial-canceled", Result: order.PartiallyCancelled},
|
||||
{Case: "LOL", Result: order.UnknownStatus},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderStatus(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToOrderSide(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Side
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "buy-limit", Result: order.Buy},
|
||||
{Case: "sell-limit", Result: order.Sell},
|
||||
{Case: "woah-nelly", Result: order.UnknownSide},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderSide(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToOrderType(t *testing.T) {
|
||||
type TestCases struct {
|
||||
Case string
|
||||
Result order.Type
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "buy-limit", Result: order.Limit},
|
||||
{Case: "sell-market", Result: order.Market},
|
||||
{Case: "woah-nelly", Result: order.UnknownType},
|
||||
}
|
||||
for i := range testCases {
|
||||
result, _ := stringToOrderType(testCases[i].Case)
|
||||
if result != testCases[i].Result {
|
||||
t.Errorf("Exepcted: %v, received: %v", testCases[i].Result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,14 +331,18 @@ type WsRequest struct {
|
||||
// WsResponse defines a response from the websocket connection when there
|
||||
// is an error
|
||||
type WsResponse struct {
|
||||
TS int64 `json:"ts"`
|
||||
Status string `json:"status"`
|
||||
ErrorCode interface{} `json:"err-code"`
|
||||
ErrorMessage string `json:"err-msg"`
|
||||
Ping int64 `json:"ping"`
|
||||
Channel string `json:"ch"`
|
||||
Subscribed string `json:"subbed"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
Op string `json:"op"`
|
||||
TS int64 `json:"ts"`
|
||||
Status string `json:"status"`
|
||||
ErrorCode int64 `json:"err-code"`
|
||||
ErrorMessage string `json:"err-msg"`
|
||||
Ping int64 `json:"ping"`
|
||||
Channel string `json:"ch"`
|
||||
Rep string `json:"rep"`
|
||||
Topic string `json:"topic"`
|
||||
Subscribed string `json:"subbed"`
|
||||
UnSubscribed string `json:"unsubbed"`
|
||||
ClientID int64 `json:"cid,string"`
|
||||
}
|
||||
|
||||
// WsHeartBeat defines a heartbeat request
|
||||
@@ -377,6 +381,7 @@ type WsKline struct {
|
||||
// WsTick stores websocket ticker data
|
||||
type WsTick struct {
|
||||
Channel string `json:"ch"`
|
||||
Rep string `json:"rep"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
Tick struct {
|
||||
Amount float64 `json:"amount"`
|
||||
@@ -478,20 +483,9 @@ type WsAuthenticatedOrdersListRequest struct {
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedDataResponse response from authenticated connection
|
||||
type WsAuthenticatedDataResponse struct {
|
||||
Op string `json:"op,omitempty"`
|
||||
Ts int64 `json:"ts,omitempty"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
ErrorCode int64 `json:"err-code,omitempty"`
|
||||
ErrorMessage string `json:"err-msg,omitempty"`
|
||||
Ping int64 `json:"ping,omitempty"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedAccountsResponse response from Accounts authenticated subscription
|
||||
type WsAuthenticatedAccountsResponse struct {
|
||||
WsAuthenticatedDataResponse
|
||||
WsResponse
|
||||
Data WsAuthenticatedAccountsResponseData `json:"data"`
|
||||
}
|
||||
|
||||
@@ -511,11 +505,11 @@ type WsAuthenticatedAccountsResponseDataList struct {
|
||||
|
||||
// WsAuthenticatedOrdersUpdateResponse response from OrdersUpdate authenticated subscription
|
||||
type WsAuthenticatedOrdersUpdateResponse struct {
|
||||
WsAuthenticatedDataResponse
|
||||
WsResponse
|
||||
Data WsAuthenticatedOrdersUpdateResponseData `json:"data"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedOrdersUpdateResponseData order updatedata
|
||||
// WsAuthenticatedOrdersUpdateResponseData order update data
|
||||
type WsAuthenticatedOrdersUpdateResponseData struct {
|
||||
UnfilledAmount float64 `json:"unfilled-amount,string"`
|
||||
FilledAmount float64 `json:"filled-amount,string"`
|
||||
@@ -526,14 +520,21 @@ type WsAuthenticatedOrdersUpdateResponseData struct {
|
||||
FilledCashAmount float64 `json:"filled-cash-amount,string"`
|
||||
Role string `json:"role"`
|
||||
OrderState string `json:"order-state"`
|
||||
OrderType string `json:"order-type"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedOrdersResponse response from Orders authenticated subscription
|
||||
type WsAuthenticatedOrdersResponse struct {
|
||||
WsAuthenticatedDataResponse
|
||||
WsResponse
|
||||
Data []WsAuthenticatedOrdersResponseData `json:"data"`
|
||||
}
|
||||
|
||||
// WsOldOrderUpdate response from Orders authenticated subscription
|
||||
type WsOldOrderUpdate struct {
|
||||
WsResponse
|
||||
Data WsAuthenticatedOrdersResponseData `json:"data"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedOrdersResponseData order data
|
||||
type WsAuthenticatedOrdersResponseData struct {
|
||||
SeqID int64 `json:"seq-id"`
|
||||
@@ -556,7 +557,7 @@ type WsAuthenticatedOrdersResponseData struct {
|
||||
|
||||
// WsAuthenticatedAccountsListResponse response from AccountsList authenticated endpoint
|
||||
type WsAuthenticatedAccountsListResponse struct {
|
||||
WsAuthenticatedDataResponse
|
||||
WsResponse
|
||||
Data []WsAuthenticatedAccountsListResponseData `json:"data"`
|
||||
}
|
||||
|
||||
@@ -577,13 +578,13 @@ type WsAuthenticatedAccountsListResponseDataList struct {
|
||||
|
||||
// WsAuthenticatedOrdersListResponse response from OrdersList authenticated endpoint
|
||||
type WsAuthenticatedOrdersListResponse struct {
|
||||
WsAuthenticatedDataResponse
|
||||
WsResponse
|
||||
Data []OrderInfo `json:"data"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedOrderDetailResponse response from OrderDetail authenticated endpoint
|
||||
type WsAuthenticatedOrderDetailResponse struct {
|
||||
WsAuthenticatedDataResponse
|
||||
WsResponse
|
||||
Data OrderInfo `json:"data"`
|
||||
}
|
||||
|
||||
@@ -591,3 +592,18 @@ type WsAuthenticatedOrderDetailResponse struct {
|
||||
type WsPong struct {
|
||||
Pong int64 `json:"pong"`
|
||||
}
|
||||
|
||||
type wsKLineResponseThing struct {
|
||||
Data []struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Close float64 `json:"close"`
|
||||
Count float64 `json:"count"`
|
||||
High float64 `json:"high"`
|
||||
ID int64 `json:"id"`
|
||||
Low float64 `json:"low"`
|
||||
Open float64 `json:"open"`
|
||||
Volume float64 `json:"vol"`
|
||||
} `json:"data"`
|
||||
Rep string `json:"rep"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -72,7 +74,7 @@ func (h *HUOBI) WsConnect() error {
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
|
||||
go h.WsHandleData()
|
||||
go h.wsReadData()
|
||||
h.GenerateDefaultSubscriptions()
|
||||
|
||||
return nil
|
||||
@@ -83,7 +85,7 @@ func (h *HUOBI) wsDial(dialer *websocket.Dialer) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go h.wsMultiConnectionFunnel(h.WebsocketConn, wsMarketURL)
|
||||
go h.wsFunnelConnectionData(h.WebsocketConn, wsMarketURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -95,12 +97,12 @@ func (h *HUOBI) wsAuthenticatedDial(dialer *websocket.Dialer) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go h.wsMultiConnectionFunnel(h.AuthenticatedWebsocketConn, wsAccountsOrdersURL)
|
||||
go h.wsFunnelConnectionData(h.AuthenticatedWebsocketConn, wsAccountsOrdersURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsMultiConnectionFunnel manages data from multiple endpoints and passes it to a channel
|
||||
func (h *HUOBI) wsMultiConnectionFunnel(ws *wshandler.WebsocketConnection, url string) {
|
||||
// wsFunnelConnectionData manages data from multiple endpoints and passes it to a channel
|
||||
func (h *HUOBI) wsFunnelConnectionData(ws *wshandler.WebsocketConnection, url string) {
|
||||
h.Websocket.Wg.Add(1)
|
||||
defer h.Websocket.Wg.Done()
|
||||
for {
|
||||
@@ -119,8 +121,8 @@ func (h *HUOBI) wsMultiConnectionFunnel(ws *wshandler.WebsocketConnection, url s
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleData handles data read from the websocket connection
|
||||
func (h *HUOBI) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (h *HUOBI) wsReadData() {
|
||||
h.Websocket.Wg.Add(1)
|
||||
defer h.Websocket.Wg.Done()
|
||||
for {
|
||||
@@ -128,120 +130,201 @@ func (h *HUOBI) WsHandleData() {
|
||||
case <-h.Websocket.ShutdownC:
|
||||
return
|
||||
case resp := <-comms:
|
||||
switch resp.URL {
|
||||
case wsMarketURL:
|
||||
h.wsHandleMarketData(resp)
|
||||
case wsAccountsOrdersURL:
|
||||
h.wsHandleAuthenticatedData(resp)
|
||||
err := h.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) {
|
||||
var init WsAuthenticatedDataResponse
|
||||
err := json.Unmarshal(resp.Raw, &init)
|
||||
func stringToOrderStatus(status string) (order.Status, error) {
|
||||
switch status {
|
||||
case "submitted":
|
||||
return order.New, nil
|
||||
case "canceled":
|
||||
return order.Cancelled, nil
|
||||
case "partial-filled":
|
||||
return order.PartiallyFilled, nil
|
||||
case "partial-canceled":
|
||||
return order.PartiallyCancelled, nil
|
||||
default:
|
||||
return order.UnknownStatus, errors.New(status + " not recognised as order status")
|
||||
}
|
||||
}
|
||||
|
||||
func stringToOrderSide(side string) (order.Side, error) {
|
||||
switch {
|
||||
case strings.Contains(side, "buy"):
|
||||
return order.Buy, nil
|
||||
case strings.Contains(side, "sell"):
|
||||
return order.Sell, nil
|
||||
}
|
||||
|
||||
return order.UnknownSide, errors.New(side + " not recognised as order side")
|
||||
}
|
||||
|
||||
func stringToOrderType(oType string) (order.Type, error) {
|
||||
switch {
|
||||
case strings.Contains(oType, "limit"):
|
||||
return order.Limit, nil
|
||||
case strings.Contains(oType, "market"):
|
||||
return order.Market, nil
|
||||
}
|
||||
|
||||
return order.UnknownType, errors.New(oType + " not recognised as order type")
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsHandleData(respRaw []byte) error {
|
||||
var init WsResponse
|
||||
err := json.Unmarshal(respRaw, &init)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
if init.Subscribed != "" ||
|
||||
init.UnSubscribed != "" ||
|
||||
init.Op == "sub" ||
|
||||
init.Op == "unsub" {
|
||||
// TODO handle subs
|
||||
return nil
|
||||
}
|
||||
if init.Ping != 0 {
|
||||
h.sendPingResponse(init.Ping)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
if init.ErrorMessage == "api-signature-not-valid" {
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
if init.Op == "sub" {
|
||||
if h.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic)
|
||||
}
|
||||
return
|
||||
return errors.New(h.Name + " - invalid credentials. Authenticated requests disabled")
|
||||
}
|
||||
if init.ClientID > 0 {
|
||||
h.AuthenticatedWebsocketConn.AddResponseWithID(init.ClientID, resp.Raw)
|
||||
return
|
||||
if h.WebsocketConn.IsIDWaitingForResponse(init.ClientID) {
|
||||
h.WebsocketConn.SetResponseIDAndData(init.ClientID, respRaw)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(init.Op, authOp):
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
var response WsAuthenticatedDataResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
err := json.Unmarshal(respRaw, &init)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
h.Websocket.DataHandler <- init
|
||||
|
||||
case strings.EqualFold(init.Topic, "accounts"):
|
||||
var response WsAuthenticatedAccountsResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
|
||||
case strings.Contains(init.Topic, "orders") &&
|
||||
strings.Contains(init.Topic, "update"):
|
||||
var response WsAuthenticatedOrdersUpdateResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
data := strings.Split(response.Topic, ".")
|
||||
if len(data) < 2 {
|
||||
return errors.New(h.Name + " - currency could not be extracted from response")
|
||||
}
|
||||
orderID := strconv.FormatInt(response.Data.OrderID, 10)
|
||||
var oSide order.Side
|
||||
oSide, err = stringToOrderSide(response.Data.OrderType)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oType order.Type
|
||||
oType, err = stringToOrderType(response.Data.OrderType)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = stringToOrderStatus(response.Data.OrderState)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = h.GetRequestFormattedPairAndAssetType(data[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- &order.Detail{
|
||||
Price: response.Data.Price,
|
||||
Amount: response.Data.UnfilledAmount + response.Data.FilledAmount,
|
||||
ExecutedAmount: response.Data.FilledAmount,
|
||||
RemainingAmount: response.Data.UnfilledAmount,
|
||||
Exchange: h.Name,
|
||||
ID: orderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
LastUpdated: time.Unix(response.TS*1000, 0),
|
||||
Pair: p,
|
||||
}
|
||||
|
||||
case strings.Contains(init.Topic, "orders"):
|
||||
var response WsAuthenticatedOrdersResponse
|
||||
err := json.Unmarshal(resp.Raw, &response)
|
||||
var response WsOldOrderUpdate
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
var init WsResponse
|
||||
err := json.Unmarshal(resp.Raw, &init)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
if init.Status == "error" {
|
||||
h.Websocket.DataHandler <- fmt.Errorf("%v %v Websocket error %s %s",
|
||||
h.Name,
|
||||
resp.URL,
|
||||
init.ErrorCode,
|
||||
init.ErrorMessage)
|
||||
return
|
||||
}
|
||||
if init.Subscribed != "" {
|
||||
return
|
||||
}
|
||||
if init.Ping != 0 {
|
||||
h.sendPingResponse(init.Ping)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(init.Channel, "depth"):
|
||||
var depth WsDepth
|
||||
err := json.Unmarshal(resp.Raw, &depth)
|
||||
err := json.Unmarshal(respRaw, &depth)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
data := strings.Split(depth.Channel, ".")
|
||||
err = h.WsProcessOrderbook(&depth, data[1])
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
case strings.Contains(init.Rep, "kline"):
|
||||
var kline wsKLineResponseThing
|
||||
err := json.Unmarshal(respRaw, &kline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var curr = strings.Split(init.Rep, ".")
|
||||
for i := range kline.Data {
|
||||
h.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Now(),
|
||||
Exchange: h.Name,
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromFormattedPairs(curr[1],
|
||||
h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)),
|
||||
OpenPrice: kline.Data[i].Open,
|
||||
ClosePrice: kline.Data[i].Close,
|
||||
HighPrice: kline.Data[i].High,
|
||||
LowPrice: kline.Data[i].Low,
|
||||
Volume: kline.Data[i].Volume,
|
||||
}
|
||||
}
|
||||
|
||||
case strings.Contains(init.Channel, "kline"):
|
||||
var kline WsKline
|
||||
err := json.Unmarshal(resp.Raw, &kline)
|
||||
err := json.Unmarshal(respRaw, &kline)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
data := strings.Split(kline.Channel, ".")
|
||||
h.Websocket.DataHandler <- wshandler.KlineData{
|
||||
@@ -258,10 +341,9 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
}
|
||||
case strings.Contains(init.Channel, "trade.detail"):
|
||||
var trade WsTrade
|
||||
err := json.Unmarshal(resp.Raw, &trade)
|
||||
err := json.Unmarshal(respRaw, &trade)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
data := strings.Split(trade.Channel, ".")
|
||||
h.Websocket.DataHandler <- wshandler.TradeData{
|
||||
@@ -271,14 +353,20 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)),
|
||||
Timestamp: time.Unix(0, trade.Tick.Timestamp*int64(time.Millisecond)),
|
||||
}
|
||||
case strings.Contains(init.Channel, "detail"):
|
||||
case strings.Contains(init.Channel, "detail"),
|
||||
strings.Contains(init.Rep, "detail"):
|
||||
var wsTicker WsTick
|
||||
err := json.Unmarshal(resp.Raw, &wsTicker)
|
||||
err := json.Unmarshal(respRaw, &wsTicker)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
var data []string
|
||||
if wsTicker.Channel != "" {
|
||||
data = strings.Split(wsTicker.Channel, ".")
|
||||
}
|
||||
if wsTicker.Rep != "" {
|
||||
data = strings.Split(wsTicker.Rep, ".")
|
||||
}
|
||||
data := strings.Split(wsTicker.Channel, ".")
|
||||
h.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: h.Name,
|
||||
Open: wsTicker.Tick.Open,
|
||||
@@ -292,7 +380,11 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
Pair: currency.NewPairFromFormattedPairs(data[1],
|
||||
h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)),
|
||||
}
|
||||
default:
|
||||
h.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: h.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUOBI) sendPingResponse(pong int64) {
|
||||
|
||||
@@ -102,6 +102,7 @@ func (h *HUOBI) SetDefaults() {
|
||||
MessageCorrelation: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
TickerFetching: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup |
|
||||
exchange.NoFiatWithdrawals,
|
||||
@@ -522,14 +523,14 @@ func (h *HUOBI) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case s.OrderSide == order.Buy && s.OrderType == order.Market:
|
||||
case s.Side == order.Buy && s.Type == order.Market:
|
||||
formattedType = SpotNewOrderRequestTypeBuyMarket
|
||||
case s.OrderSide == order.Sell && s.OrderType == order.Market:
|
||||
case s.Side == order.Sell && s.Type == order.Market:
|
||||
formattedType = SpotNewOrderRequestTypeSellMarket
|
||||
case s.OrderSide == order.Buy && s.OrderType == order.Limit:
|
||||
case s.Side == order.Buy && s.Type == order.Limit:
|
||||
formattedType = SpotNewOrderRequestTypeBuyLimit
|
||||
params.Price = s.Price
|
||||
case s.OrderSide == order.Sell && s.OrderType == order.Limit:
|
||||
case s.Side == order.Sell && s.Type == order.Limit:
|
||||
formattedType = SpotNewOrderRequestTypeSellLimit
|
||||
params.Price = s.Price
|
||||
}
|
||||
@@ -544,7 +545,7 @@ func (h *HUOBI) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
return submitOrderResponse, nil
|
||||
@@ -558,7 +559,7 @@ func (h *HUOBI) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (h *HUOBI) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -616,33 +617,68 @@ func (h *HUOBI) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
if respData.ID == 0 {
|
||||
return orderDetail, fmt.Errorf("%s - order not found for orderid %s", h.Name, orderID)
|
||||
}
|
||||
|
||||
var responseID = strconv.FormatInt(respData.ID, 10)
|
||||
if responseID != orderID {
|
||||
return orderDetail, errors.New(h.Name + " - GetOrderInfo orderID mismatch. Expected: " + orderID + " Received: " + responseID)
|
||||
}
|
||||
typeDetails := strings.Split(respData.Type, "-")
|
||||
orderSide, err := order.StringToOrderSide(typeDetails[0])
|
||||
if err != nil {
|
||||
return orderDetail, err
|
||||
if h.Websocket.IsConnected() {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
} else {
|
||||
return orderDetail, err
|
||||
}
|
||||
}
|
||||
orderType, err := order.StringToOrderType(typeDetails[1])
|
||||
if err != nil {
|
||||
return orderDetail, err
|
||||
if h.Websocket.IsConnected() {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
} else {
|
||||
return orderDetail, err
|
||||
}
|
||||
}
|
||||
orderStatus, err := order.StringToOrderStatus(respData.State)
|
||||
if err != nil {
|
||||
if h.Websocket.IsConnected() {
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
} else {
|
||||
return orderDetail, err
|
||||
}
|
||||
}
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = h.GetRequestFormattedPairAndAssetType(respData.Symbol)
|
||||
if err != nil {
|
||||
return orderDetail, err
|
||||
}
|
||||
|
||||
orderDetail = order.Detail{
|
||||
Exchange: h.Name,
|
||||
ID: strconv.FormatInt(respData.ID, 10),
|
||||
ID: orderID,
|
||||
AccountID: strconv.FormatInt(respData.AccountID, 10),
|
||||
CurrencyPair: currency.NewPairFromString(respData.Symbol),
|
||||
OrderType: orderType,
|
||||
OrderSide: orderSide,
|
||||
OrderDate: time.Unix(0, respData.CreatedAt*int64(time.Millisecond)),
|
||||
Pair: p,
|
||||
Type: orderType,
|
||||
Side: orderSide,
|
||||
Date: time.Unix(0, respData.CreatedAt*int64(time.Millisecond)),
|
||||
Status: orderStatus,
|
||||
Price: respData.Price,
|
||||
Amount: respData.Amount,
|
||||
ExecutedAmount: respData.FilledAmount,
|
||||
Fee: respData.FilledFees,
|
||||
AssetType: a,
|
||||
}
|
||||
return orderDetail, nil
|
||||
}
|
||||
@@ -693,48 +729,61 @@ func (h *HUOBI) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("currency must be supplied")
|
||||
}
|
||||
|
||||
side := ""
|
||||
if req.OrderSide == order.AnySide || req.OrderSide == "" {
|
||||
if req.Side == order.AnySide || req.Side == "" {
|
||||
side = ""
|
||||
} else if req.OrderSide == order.Sell {
|
||||
side = req.OrderSide.Lower()
|
||||
} else if req.Side == order.Sell {
|
||||
side = req.Side.Lower()
|
||||
}
|
||||
|
||||
var orders []order.Detail
|
||||
|
||||
if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
for i := range req.Currencies {
|
||||
resp, err := h.wsGetOrdersList(-1, req.Currencies[i])
|
||||
for i := range req.Pairs {
|
||||
resp, err := h.wsGetOrdersList(-1, req.Pairs[i])
|
||||
if err != nil {
|
||||
return orders, err
|
||||
}
|
||||
for j := range resp.Data {
|
||||
sideData := strings.Split(resp.Data[j].OrderState, "-")
|
||||
side = sideData[0]
|
||||
var orderID = strconv.FormatInt(resp.Data[j].OrderID, 10)
|
||||
orderSide, err := order.StringToOrderSide(side)
|
||||
if err != nil {
|
||||
return orders, err
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
orderType, err := order.StringToOrderType(sideData[1])
|
||||
if err != nil {
|
||||
return orders, err
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
orderStatus, err := order.StringToOrderStatus(resp.Data[j].OrderState)
|
||||
if err != nil {
|
||||
return orders, err
|
||||
h.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: h.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
orders = append(orders, order.Detail{
|
||||
Exchange: h.Name,
|
||||
AccountID: strconv.FormatInt(resp.Data[j].AccountID, 10),
|
||||
ID: strconv.FormatInt(resp.Data[j].OrderID, 10),
|
||||
CurrencyPair: req.Currencies[i],
|
||||
OrderType: orderType,
|
||||
OrderSide: orderSide,
|
||||
OrderDate: time.Unix(0, resp.Data[j].CreatedAt*int64(time.Millisecond)),
|
||||
ID: orderID,
|
||||
Pair: req.Pairs[i],
|
||||
Type: orderType,
|
||||
Side: orderSide,
|
||||
Date: time.Unix(0, resp.Data[j].CreatedAt*int64(time.Millisecond)),
|
||||
Status: orderStatus,
|
||||
Price: resp.Data[j].Price,
|
||||
Amount: resp.Data[j].OrderAmount,
|
||||
@@ -745,9 +794,9 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := range req.Currencies {
|
||||
for i := range req.Pairs {
|
||||
resp, err := h.GetOpenOrders(h.API.Credentials.ClientID,
|
||||
req.Currencies[i].Lower().String(),
|
||||
req.Pairs[i].Lower().String(),
|
||||
side,
|
||||
500)
|
||||
if err != nil {
|
||||
@@ -759,10 +808,10 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
Amount: resp[i].Amount,
|
||||
CurrencyPair: req.Currencies[i],
|
||||
Pair: req.Pairs[i],
|
||||
Exchange: h.Name,
|
||||
ExecutedAmount: resp[i].FilledAmount,
|
||||
OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)),
|
||||
Date: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)),
|
||||
Status: order.Status(resp[i].State),
|
||||
AccountID: strconv.FormatInt(resp[i].AccountID, 10),
|
||||
Fee: resp[i].FilledFees,
|
||||
@@ -782,14 +831,14 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Currencies) == 0 {
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("currency must be supplied")
|
||||
}
|
||||
|
||||
states := "partial-canceled,filled,canceled"
|
||||
var orders []order.Detail
|
||||
for i := range req.Currencies {
|
||||
resp, err := h.GetOrders(req.Currencies[i].Lower().String(),
|
||||
for i := range req.Pairs {
|
||||
resp, err := h.GetOrders(req.Pairs[i].Lower().String(),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
@@ -806,10 +855,10 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
Amount: resp[i].Amount,
|
||||
CurrencyPair: req.Currencies[i],
|
||||
Pair: req.Pairs[i],
|
||||
Exchange: h.Name,
|
||||
ExecutedAmount: resp[i].FilledAmount,
|
||||
OrderDate: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)),
|
||||
Date: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)),
|
||||
Status: order.Status(resp[i].State),
|
||||
AccountID: strconv.FormatInt(resp[i].AccountID, 10),
|
||||
Fee: resp[i].FilledFees,
|
||||
@@ -828,17 +877,17 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er
|
||||
func setOrderSideAndType(requestType string, orderDetail *order.Detail) {
|
||||
switch SpotNewOrderRequestParamsType(requestType) {
|
||||
case SpotNewOrderRequestTypeBuyMarket:
|
||||
orderDetail.OrderSide = order.Buy
|
||||
orderDetail.OrderType = order.Market
|
||||
orderDetail.Side = order.Buy
|
||||
orderDetail.Type = order.Market
|
||||
case SpotNewOrderRequestTypeSellMarket:
|
||||
orderDetail.OrderSide = order.Sell
|
||||
orderDetail.OrderType = order.Market
|
||||
orderDetail.Side = order.Sell
|
||||
orderDetail.Type = order.Market
|
||||
case SpotNewOrderRequestTypeBuyLimit:
|
||||
orderDetail.OrderSide = order.Buy
|
||||
orderDetail.OrderType = order.Limit
|
||||
orderDetail.Side = order.Buy
|
||||
orderDetail.Type = order.Limit
|
||||
case SpotNewOrderRequestTypeSellLimit:
|
||||
orderDetail.OrderSide = order.Sell
|
||||
orderDetail.OrderType = order.Limit
|
||||
orderDetail.Side = order.Sell
|
||||
orderDetail.Type = order.Limit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := i.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -280,7 +280,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := i.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -307,11 +307,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := i.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -328,10 +328,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := i.CancelOrder(orderCancellation)
|
||||
@@ -351,10 +351,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := i.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -340,8 +340,8 @@ func (i *ItBit) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
response, err := i.PlaceOrder(wallet,
|
||||
s.OrderSide.String(),
|
||||
s.OrderType.String(),
|
||||
s.Side.String(),
|
||||
s.Type.String(),
|
||||
s.Pair.Base.String(),
|
||||
s.Amount,
|
||||
s.Price,
|
||||
@@ -369,7 +369,7 @@ func (i *ItBit) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (i *ItBit) CancelOrder(order *order.Cancel) error {
|
||||
return i.CancelExistingOrder(order.WalletAddress, order.OrderID)
|
||||
return i.CancelExistingOrder(order.WalletAddress, order.ID)
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
@@ -471,19 +471,19 @@ func (i *ItBit) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
ID: allOrders[j].ID,
|
||||
OrderSide: side,
|
||||
Side: side,
|
||||
Amount: allOrders[j].Amount,
|
||||
ExecutedAmount: allOrders[j].AmountFilled,
|
||||
RemainingAmount: (allOrders[j].Amount - allOrders[j].AmountFilled),
|
||||
Exchange: i.Name,
|
||||
OrderDate: orderDate,
|
||||
CurrencyPair: symbol,
|
||||
Date: orderDate,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -525,19 +525,19 @@ func (i *ItBit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
ID: allOrders[j].ID,
|
||||
OrderSide: side,
|
||||
Side: side,
|
||||
Amount: allOrders[j].Amount,
|
||||
ExecutedAmount: allOrders[j].AmountFilled,
|
||||
RemainingAmount: (allOrders[j].Amount - allOrders[j].AmountFilled),
|
||||
Exchange: i.Name,
|
||||
OrderDate: orderDate,
|
||||
CurrencyPair: symbol,
|
||||
Date: orderDate,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("Kraken setup error", err)
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
k.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -381,7 +382,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
// TestGetActiveOrders wrapper test
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := k.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -395,7 +396,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
// TestGetOrderHistory wrapper test
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := k.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -438,11 +439,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.XBT,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := k.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -460,10 +461,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := k.CancelOrder(orderCancellation)
|
||||
@@ -483,10 +484,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := k.CancelAllOrders(orderCancellation)
|
||||
@@ -680,9 +681,9 @@ func setupWsTests(t *testing.T) {
|
||||
}
|
||||
authToken = token
|
||||
|
||||
go k.WsReadData(k.WebsocketConn)
|
||||
go k.WsReadData(k.AuthenticatedWebsocketConn)
|
||||
go k.WsHandleData()
|
||||
go k.wsFunnelConnectionData(k.WebsocketConn)
|
||||
go k.wsFunnelConnectionData(k.AuthenticatedWebsocketConn)
|
||||
go k.wsReadData()
|
||||
go k.wsPingHandler()
|
||||
wsSetupRan = true
|
||||
}
|
||||
@@ -733,3 +734,624 @@ func TestWsCancelOrder(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsPong(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"event": "pong",
|
||||
"reqid": 42
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSystemStatus(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"connectionID": 8628615390848610000,
|
||||
"event": "systemStatus",
|
||||
"status": "online",
|
||||
"version": "1.0.0"
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSubscriptionStatus(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"channelID": 10001,
|
||||
"channelName": "ticker",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/EUR",
|
||||
"status": "subscribed",
|
||||
"subscription": {
|
||||
"name": "ticker"
|
||||
}
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"channelID": 10001,
|
||||
"channelName": "ohlc-5",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/EUR",
|
||||
"reqid": 42,
|
||||
"status": "unsubscribed",
|
||||
"subscription": {
|
||||
"interval": 5,
|
||||
"name": "ohlc"
|
||||
}
|
||||
}`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"channelName": "ownTrades",
|
||||
"event": "subscriptionStatus",
|
||||
"status": "subscribed",
|
||||
"subscription": {
|
||||
"name": "ownTrades"
|
||||
}
|
||||
}`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`{
|
||||
"errorMessage": "Subscription depth not supported",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/USD",
|
||||
"status": "error",
|
||||
"subscription": {
|
||||
"depth": 42,
|
||||
"name": "book"
|
||||
}
|
||||
}`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTicker(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"channelID": 1337,
|
||||
"channelName": "ticker",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/EUR",
|
||||
"status": "subscribed",
|
||||
"subscription": {
|
||||
"name": "ticker"
|
||||
}
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
1337,
|
||||
{
|
||||
"a": [
|
||||
"5525.40000",
|
||||
1,
|
||||
"1.000"
|
||||
],
|
||||
"b": [
|
||||
"5525.10000",
|
||||
1,
|
||||
"1.000"
|
||||
],
|
||||
"c": [
|
||||
"5525.10000",
|
||||
"0.00398963"
|
||||
],
|
||||
"h": [
|
||||
"5783.00000",
|
||||
"5783.00000"
|
||||
],
|
||||
"l": [
|
||||
"5505.00000",
|
||||
"5505.00000"
|
||||
],
|
||||
"o": [
|
||||
"5760.70000",
|
||||
"5763.40000"
|
||||
],
|
||||
"p": [
|
||||
"5631.44067",
|
||||
"5653.78939"
|
||||
],
|
||||
"t": [
|
||||
11493,
|
||||
16267
|
||||
],
|
||||
"v": [
|
||||
"2634.11501494",
|
||||
"3591.17907851"
|
||||
]
|
||||
},
|
||||
"ticker",
|
||||
"XBT/USD"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOHLC(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"channelID": 13337,
|
||||
"channelName": "ohlc",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/EUR",
|
||||
"status": "subscribed",
|
||||
"subscription": {
|
||||
"name": "ohlc"
|
||||
}
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
13337,
|
||||
[
|
||||
"1542057314.748456",
|
||||
"1542057360.435743",
|
||||
"3586.70000",
|
||||
"3586.70000",
|
||||
"3586.60000",
|
||||
"3586.60000",
|
||||
"3586.68894",
|
||||
"0.03373000",
|
||||
2
|
||||
],
|
||||
"ohlc-5",
|
||||
"XBT/USD"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsTrade(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"channelID": 133337,
|
||||
"channelName": "trade",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/EUR",
|
||||
"status": "subscribed",
|
||||
"subscription": {
|
||||
"name": "trade"
|
||||
}
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
133337,
|
||||
[
|
||||
[
|
||||
"5541.20000",
|
||||
"0.15850568",
|
||||
"1534614057.321597",
|
||||
"s",
|
||||
"l",
|
||||
""
|
||||
],
|
||||
[
|
||||
"6060.00000",
|
||||
"0.02455000",
|
||||
"1534614057.324998",
|
||||
"b",
|
||||
"l",
|
||||
""
|
||||
]
|
||||
],
|
||||
"trade",
|
||||
"XBT/USD"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsSpread(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"channelID": 1333337,
|
||||
"channelName": "spread",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/EUR",
|
||||
"status": "subscribed",
|
||||
"subscription": {
|
||||
"name": "spread"
|
||||
}
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
1333337,
|
||||
[
|
||||
"5698.40000",
|
||||
"5700.00000",
|
||||
"1542057299.545897",
|
||||
"1.01234567",
|
||||
"0.98765432"
|
||||
],
|
||||
"spread",
|
||||
"XBT/USD"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOrdrbook(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"channelID": 13333337,
|
||||
"channelName": "book",
|
||||
"event": "subscriptionStatus",
|
||||
"pair": "XBT/EUR",
|
||||
"status": "subscribed",
|
||||
"subscription": {
|
||||
"name": "book"
|
||||
}
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
13333337,
|
||||
{
|
||||
"as": [
|
||||
[
|
||||
"5541.30000",
|
||||
"2.50700000",
|
||||
"1534614248.123678"
|
||||
],
|
||||
[
|
||||
"5541.80000",
|
||||
"0.33000000",
|
||||
"1534614098.345543"
|
||||
],
|
||||
[
|
||||
"5542.70000",
|
||||
"0.64700000",
|
||||
"1534614244.654432"
|
||||
]
|
||||
],
|
||||
"bs": [
|
||||
[
|
||||
"5541.20000",
|
||||
"1.52900000",
|
||||
"1534614248.765567"
|
||||
],
|
||||
[
|
||||
"5539.90000",
|
||||
"0.30000000",
|
||||
"1534614241.769870"
|
||||
],
|
||||
[
|
||||
"5539.50000",
|
||||
"5.00000000",
|
||||
"1534613831.243486"
|
||||
]
|
||||
]
|
||||
},
|
||||
"book-100",
|
||||
"XBT/USD"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
13333337,
|
||||
{
|
||||
"a": [
|
||||
[
|
||||
"5541.30000",
|
||||
"2.50700000",
|
||||
"1534614248.456738"
|
||||
],
|
||||
[
|
||||
"5542.50000",
|
||||
"0.40100000",
|
||||
"1534614248.456738"
|
||||
]
|
||||
]
|
||||
},
|
||||
"book-10",
|
||||
"XBT/USD"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
13333337,
|
||||
{
|
||||
"b": [
|
||||
[
|
||||
"5541.30000",
|
||||
"0.00000000",
|
||||
"1534614335.345903"
|
||||
]
|
||||
]
|
||||
},
|
||||
"book-10",
|
||||
"XBT/USD"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOwnTrades(t *testing.T) {
|
||||
pressXToJSON := []byte(`[
|
||||
[
|
||||
{
|
||||
"TDLH43-DVQXD-2KHVYY": {
|
||||
"cost": "1000000.00000",
|
||||
"fee": "1600.00000",
|
||||
"margin": "0.00000",
|
||||
"ordertxid": "TDLH43-DVQXD-2KHVYY",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"postxid": "OGTT3Y-C6I3P-XRI6HX",
|
||||
"price": "100000.00000",
|
||||
"time": "1560516023.070651",
|
||||
"type": "sell",
|
||||
"vol": "1000000000.00000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TDLH43-DVQXD-2KHVYY": {
|
||||
"cost": "1000000.00000",
|
||||
"fee": "600.00000",
|
||||
"margin": "0.00000",
|
||||
"ordertxid": "TDLH43-DVQXD-2KHVYY",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"postxid": "OGTT3Y-C6I3P-XRI6HX",
|
||||
"price": "100000.00000",
|
||||
"time": "1560516023.070658",
|
||||
"type": "buy",
|
||||
"vol": "1000000000.00000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TDLH43-DVQXD-2KHVYY": {
|
||||
"cost": "1000000.00000",
|
||||
"fee": "1600.00000",
|
||||
"margin": "0.00000",
|
||||
"ordertxid": "TDLH43-DVQXD-2KHVYY",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"postxid": "OGTT3Y-C6I3P-XRI6HX",
|
||||
"price": "100000.00000",
|
||||
"time": "1560520332.914657",
|
||||
"type": "sell",
|
||||
"vol": "1000000000.00000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TDLH43-DVQXD-2KHVYY": {
|
||||
"cost": "1000000.00000",
|
||||
"fee": "600.00000",
|
||||
"margin": "0.00000",
|
||||
"ordertxid": "TDLH43-DVQXD-2KHVYY",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"postxid": "OGTT3Y-C6I3P-XRI6HX",
|
||||
"price": "100000.00000",
|
||||
"time": "1560520332.914664",
|
||||
"type": "buy",
|
||||
"vol": "1000000000.00000000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ownTrades"
|
||||
]`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsOpenOrders(t *testing.T) {
|
||||
pressXToJSON := []byte(`[
|
||||
[
|
||||
{
|
||||
"OGTT3Y-C6I3P-XRI6HX": {
|
||||
"cost": "0.00000",
|
||||
"descr": {
|
||||
"close": "",
|
||||
"leverage": "0:1",
|
||||
"order": "sell 10.00345345 XBT/USD @ limit 34.50000 with 0:1 leverage",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"price": "34.50000",
|
||||
"price2": "0.00000",
|
||||
"type": "sell"
|
||||
},
|
||||
"expiretm": "0.000000",
|
||||
"fee": "0.00000",
|
||||
"limitprice": "34.50000",
|
||||
"misc": "",
|
||||
"oflags": "fcib",
|
||||
"opentm": "0.000000",
|
||||
"price": "34.50000",
|
||||
"refid": "OKIVMP-5GVZN-Z2D2UA",
|
||||
"starttm": "0.000000",
|
||||
"status": "open",
|
||||
"stopprice": "0.000000",
|
||||
"userref": 0,
|
||||
"vol": "10.00345345",
|
||||
"vol_exec": "0.00000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"OGTT3Y-C6I3P-XRI6HX": {
|
||||
"cost": "0.00000",
|
||||
"descr": {
|
||||
"close": "",
|
||||
"leverage": "0:1",
|
||||
"order": "sell 0.00000010 XBT/USD @ limit 5334.60000 with 0:1 leverage",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"price": "5334.60000",
|
||||
"price2": "0.00000",
|
||||
"type": "sell"
|
||||
},
|
||||
"expiretm": "0.000000",
|
||||
"fee": "0.00000",
|
||||
"limitprice": "5334.60000",
|
||||
"misc": "",
|
||||
"oflags": "fcib",
|
||||
"opentm": "0.000000",
|
||||
"price": "5334.60000",
|
||||
"refid": "OKIVMP-5GVZN-Z2D2UA",
|
||||
"starttm": "0.000000",
|
||||
"status": "open",
|
||||
"stopprice": "0.000000",
|
||||
"userref": 0,
|
||||
"vol": "0.00000010",
|
||||
"vol_exec": "0.00000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"OGTT3Y-C6I3P-XRI6HX": {
|
||||
"cost": "0.00000",
|
||||
"descr": {
|
||||
"close": "",
|
||||
"leverage": "0:1",
|
||||
"order": "sell 0.00001000 XBT/USD @ limit 90.40000 with 0:1 leverage",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"price": "90.40000",
|
||||
"price2": "0.00000",
|
||||
"type": "sell"
|
||||
},
|
||||
"expiretm": "0.000000",
|
||||
"fee": "0.00000",
|
||||
"limitprice": "90.40000",
|
||||
"misc": "",
|
||||
"oflags": "fcib",
|
||||
"opentm": "0.000000",
|
||||
"price": "90.40000",
|
||||
"refid": "OKIVMP-5GVZN-Z2D2UA",
|
||||
"starttm": "0.000000",
|
||||
"status": "open",
|
||||
"stopprice": "0.000000",
|
||||
"userref": 0,
|
||||
"vol": "0.00001000",
|
||||
"vol_exec": "0.00000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"OGTT3Y-C6I3P-XRI6HX": {
|
||||
"cost": "0.00000",
|
||||
"descr": {
|
||||
"close": "",
|
||||
"leverage": "0:1",
|
||||
"order": "sell 0.00001000 XBT/USD @ limit 9.00000 with 0:1 leverage",
|
||||
"ordertype": "limit",
|
||||
"pair": "XBT/USD",
|
||||
"price": "9.00000",
|
||||
"price2": "0.00000",
|
||||
"type": "sell"
|
||||
},
|
||||
"expiretm": "0.000000",
|
||||
"fee": "0.00000",
|
||||
"limitprice": "9.00000",
|
||||
"misc": "",
|
||||
"oflags": "fcib",
|
||||
"opentm": "0.000000",
|
||||
"price": "9.00000",
|
||||
"refid": "OKIVMP-5GVZN-Z2D2UA",
|
||||
"starttm": "0.000000",
|
||||
"status": "open",
|
||||
"stopprice": "0.000000",
|
||||
"userref": 0,
|
||||
"vol": "0.00001000",
|
||||
"vol_exec": "0.00000000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"openOrders"
|
||||
]`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pressXToJSON = []byte(`[
|
||||
[
|
||||
{
|
||||
"OGTT3Y-C6I3P-XRI6HX": {
|
||||
"status": "closed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"OGTT3Y-C6I3P-XRI6HX": {
|
||||
"status": "closed"
|
||||
}
|
||||
}
|
||||
],
|
||||
"openOrders"
|
||||
]`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsAddOrderJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"descr": "buy 0.01770000 XBTUSD @ limit 4000",
|
||||
"event": "addOrderStatus",
|
||||
"status": "ok",
|
||||
"txid": "ONPNXH-KMKMU-F4MR5V"
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
pressXToJSON = []byte(`{
|
||||
"errorMessage": "EOrder:Order minimum not met",
|
||||
"event": "addOrderStatus",
|
||||
"status": "error"
|
||||
}`)
|
||||
err = k.wsHandleData(pressXToJSON)
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsCancelOrderJSON(t *testing.T) {
|
||||
pressXToJSON := []byte(`{
|
||||
"event": "cancelOrderStatus",
|
||||
"status": "ok"
|
||||
}`)
|
||||
err := k.wsHandleData(pressXToJSON)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +430,6 @@ type WebsocketEventResponse struct {
|
||||
Subscription WebsocketSubscriptionResponseData `json:"subscription,omitempty"`
|
||||
ChannelName string `json:"channelName,omitempty"`
|
||||
WebsocketSubscriptionEventResponse
|
||||
WebsocketStatusResponse
|
||||
WebsocketErrorResponse
|
||||
}
|
||||
|
||||
@@ -444,12 +443,6 @@ type WebsocketSubscriptionResponseData struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// WebsocketStatusResponse defines a websocket status response
|
||||
type WebsocketStatusResponse struct {
|
||||
ConnectionID float64 `json:"connectionID"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// WebsocketDataResponse defines a websocket data type
|
||||
type WebsocketDataResponse []interface{}
|
||||
|
||||
@@ -475,19 +468,70 @@ type WsTokenResponse struct {
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type wsSystemStatus struct {
|
||||
ConnectionID float64 `json:"connectionID"`
|
||||
Event string `json:"event"`
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type wsSubscription struct {
|
||||
ChannelID int64 `json:"channelID"`
|
||||
ChannelName string `json:"channelName"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
Event string `json:"event"`
|
||||
Pair string `json:"pair"`
|
||||
RequestID int64 `json:"reqid"`
|
||||
Status string `json:"status"`
|
||||
Subscription struct {
|
||||
Depth int `json:"depth"`
|
||||
Interval int `json:"interval"`
|
||||
Name string `json:"name"`
|
||||
} `json:"subscription"`
|
||||
}
|
||||
|
||||
// WsOpenOrder contains all open order data from ws feed
|
||||
type WsOpenOrder struct {
|
||||
UserReferenceID int64 `json:"userref"`
|
||||
ExpireTime float64 `json:"expiretm,string"`
|
||||
OpenTime float64 `json:"opentm,string"`
|
||||
StartTime float64 `json:"starttm,string"`
|
||||
Fee float64 `json:"fee,string"`
|
||||
LimitPrice float64 `json:"limitprice,string"`
|
||||
StopPrice float64 `json:"stopprice,string"`
|
||||
Volume float64 `json:"vol,string"`
|
||||
ExecutedVolume float64 `json:"vol_exec,string"`
|
||||
Cost float64 `json:"cost,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
Misc string `json:"misc"`
|
||||
OFlags string `json:"oflags"`
|
||||
RefID string `json:"refid"`
|
||||
Status string `json:"status"`
|
||||
Description struct {
|
||||
Close string `json:"close"`
|
||||
Price float64 `json:"price,string"`
|
||||
Price2 float64 `json:"price2,string"`
|
||||
Leverage string `json:"leverage"`
|
||||
Order string `json:"order"`
|
||||
OrderType string `json:"ordertype"`
|
||||
Pair string `json:"pair"`
|
||||
Type string `json:"type"`
|
||||
} `json:"descr"`
|
||||
}
|
||||
|
||||
// WsOwnTrade ws auth owntrade data
|
||||
type WsOwnTrade struct {
|
||||
Cost float64 `json:"cost,string"`
|
||||
Fee float64 `json:"fee,string"`
|
||||
Margin float64 `json:"margin,string"`
|
||||
OrderTransactionID string `json:"ordertxid"`
|
||||
OrderType string `json:"ordertype"`
|
||||
Pair string `json:"pair"`
|
||||
PostTransactionID string `json:"postxid"`
|
||||
Price float64 `json:"price,string"`
|
||||
Time time.Time `json:"time"`
|
||||
Type string `json:"type"`
|
||||
Vol float64 `json:"vol,string"`
|
||||
Cost float64 `json:"cost,string"`
|
||||
Fee float64 `json:"fee,string"`
|
||||
Margin float64 `json:"margin,string"`
|
||||
OrderTransactionID string `json:"ordertxid"`
|
||||
OrderType string `json:"ordertype"`
|
||||
Pair string `json:"pair"`
|
||||
PostTransactionID string `json:"postxid"`
|
||||
Price float64 `json:"price,string"`
|
||||
Time float64 `json:"time,string"`
|
||||
Type string `json:"type"`
|
||||
Vol float64 `json:"vol,string"`
|
||||
}
|
||||
|
||||
// WsOpenOrders ws auth open order data
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -26,7 +28,7 @@ const (
|
||||
krakenWSURL = "wss://ws.kraken.com"
|
||||
krakenAuthWSURL = "wss://ws-auth.kraken.com"
|
||||
krakenWSSandboxURL = "wss://sandbox.kraken.com"
|
||||
krakenWSSupportedVersion = "0.3.0"
|
||||
krakenWSSupportedVersion = "1.0.0"
|
||||
// WS endpoints
|
||||
krakenWsHeartbeat = "heartbeat"
|
||||
krakenWsSystemStatus = "systemStatus"
|
||||
@@ -42,6 +44,8 @@ const (
|
||||
krakenWsOpenOrders = "openOrders"
|
||||
krakenWsAddOrder = "addOrder"
|
||||
krakenWsCancelOrder = "cancelOrder"
|
||||
krakenWsAddOrderStatus = "addOrderStatus"
|
||||
krakenWsCancelOrderStatus = "cancelOrderStatus"
|
||||
krakenWsRateLimit = 50
|
||||
krakenWsPingDelay = time.Second * 27
|
||||
)
|
||||
@@ -78,12 +82,12 @@ func (k *Kraken) WsConnect() error {
|
||||
k.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
log.Errorf(log.ExchangeSys, "%v - failed to connect to authenticated endpoint: %v\n", k.Name, err)
|
||||
}
|
||||
go k.WsReadData(k.AuthenticatedWebsocketConn)
|
||||
go k.wsFunnelConnectionData(k.AuthenticatedWebsocketConn)
|
||||
k.GenerateAuthenticatedSubscriptions()
|
||||
}
|
||||
|
||||
go k.WsReadData(k.WebsocketConn)
|
||||
go k.WsHandleData()
|
||||
go k.wsFunnelConnectionData(k.WebsocketConn)
|
||||
go k.wsReadData()
|
||||
err = k.wsPingHandler()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v - failed setup ping handler. Websocket may disconnect unexpectedly. %v\n", k.Name, err)
|
||||
@@ -93,8 +97,8 @@ func (k *Kraken) WsConnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsReadData funnels both auth and public ws data into one manageable place
|
||||
func (k *Kraken) WsReadData(ws *wshandler.WebsocketConnection) {
|
||||
// wsFunnelConnectionData funnels both auth and public ws data into one manageable place
|
||||
func (k *Kraken) wsFunnelConnectionData(ws *wshandler.WebsocketConnection) {
|
||||
k.Websocket.Wg.Add(1)
|
||||
defer k.Websocket.Wg.Done()
|
||||
for {
|
||||
@@ -113,8 +117,8 @@ func (k *Kraken) WsReadData(ws *wshandler.WebsocketConnection) {
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleData handles the read data from the websocket connection
|
||||
func (k *Kraken) WsHandleData() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (k *Kraken) wsReadData() {
|
||||
k.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
k.Websocket.Wg.Done()
|
||||
@@ -126,30 +130,96 @@ func (k *Kraken) WsHandleData() {
|
||||
return
|
||||
default:
|
||||
resp := <-comms
|
||||
// event response handling
|
||||
var eventResponse WebsocketEventResponse
|
||||
err := json.Unmarshal(resp.Raw, &eventResponse)
|
||||
if err == nil && eventResponse.Event != "" {
|
||||
k.WsHandleEventResponse(&eventResponse, resp.Raw)
|
||||
continue
|
||||
}
|
||||
// Data response handling
|
||||
var dataResponse WebsocketDataResponse
|
||||
err = json.Unmarshal(resp.Raw, &dataResponse)
|
||||
err := k.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
log.Error(log.WebsocketMgr, fmt.Errorf("%s - unhandled websocket data: %v", k.Name, err))
|
||||
continue
|
||||
}
|
||||
if _, ok := dataResponse[0].(float64); ok {
|
||||
k.WsHandleDataResponse(dataResponse)
|
||||
}
|
||||
if _, ok := dataResponse[1].(string); ok {
|
||||
k.wsHandleAuthDataResponse(dataResponse)
|
||||
k.Websocket.DataHandler <- fmt.Errorf("%s - unhandled websocket data: %v", k.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) wsHandleData(respRaw []byte) error {
|
||||
if strings.HasPrefix(string(respRaw), "[") {
|
||||
var dataResponse WebsocketDataResponse
|
||||
err := json.Unmarshal(respRaw, &dataResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := dataResponse[0].(float64); ok {
|
||||
err = k.wsReadDataResponse(dataResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := dataResponse[1].(string); ok {
|
||||
err = k.wsHandleAuthDataResponse(dataResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var eventResponse map[string]interface{}
|
||||
err := json.Unmarshal(respRaw, &eventResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s - err %s could not parse websocket data: %s", k.Name, err, respRaw)
|
||||
}
|
||||
if event, ok := eventResponse["event"]; ok {
|
||||
switch event {
|
||||
case wshandler.Pong, krakenWsHeartbeat, krakenWsCancelOrderStatus:
|
||||
return nil
|
||||
case krakenWsSystemStatus:
|
||||
var systemStatus wsSystemStatus
|
||||
err := json.Unmarshal(respRaw, &systemStatus)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s - err %s unable to parse system status response: %s", k.Name, err, respRaw)
|
||||
}
|
||||
if systemStatus.Status != "online" {
|
||||
k.Websocket.DataHandler <- fmt.Errorf("%v Websocket status '%v'",
|
||||
k.Name, systemStatus.Status)
|
||||
}
|
||||
if systemStatus.Version > krakenWSSupportedVersion {
|
||||
log.Warnf(log.ExchangeSys, "%v New version of Websocket API released. Was %v Now %v",
|
||||
k.Name, krakenWSSupportedVersion, systemStatus.Version)
|
||||
}
|
||||
case krakenWsAddOrderStatus:
|
||||
var status WsAddOrderResponse
|
||||
err := json.Unmarshal(respRaw, &status)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s - err %s unable to parse add order response: %s", k.Name, err, respRaw)
|
||||
}
|
||||
if status.ErrorMessage != "" {
|
||||
return fmt.Errorf("%s - err %s", k.Name, status.ErrorMessage)
|
||||
}
|
||||
k.Websocket.DataHandler <- &order.Detail{
|
||||
Exchange: k.Name,
|
||||
ID: status.TransactionID,
|
||||
Status: order.New,
|
||||
}
|
||||
case krakenWsSubscriptionStatus:
|
||||
var sub wsSubscription
|
||||
err := json.Unmarshal(respRaw, &sub)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s - err %s unable to parse subscription response: %s", k.Name, err, respRaw)
|
||||
}
|
||||
if sub.Status != "subscribed" && sub.Status != "unsubscribed" {
|
||||
return fmt.Errorf("%v %v %v", k.Name, sub.RequestID, sub.ErrorMessage)
|
||||
}
|
||||
k.addNewSubscriptionChannelData(&sub)
|
||||
if sub.RequestID > 0 {
|
||||
if k.WebsocketConn.IsIDWaitingForResponse(sub.RequestID) {
|
||||
k.WebsocketConn.SetResponseIDAndData(sub.RequestID, respRaw)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
k.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: k.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket
|
||||
func (k *Kraken) wsPingHandler() error {
|
||||
message, err := json.Marshal(pingRequest)
|
||||
@@ -164,278 +234,193 @@ func (k *Kraken) wsPingHandler() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleDataResponse classifies the WS response and sends to appropriate handler
|
||||
func (k *Kraken) WsHandleDataResponse(response WebsocketDataResponse) {
|
||||
// wsReadDataResponse classifies the WS response and sends to appropriate handler
|
||||
func (k *Kraken) wsReadDataResponse(response WebsocketDataResponse) error {
|
||||
if cID, ok := response[0].(float64); ok {
|
||||
channelID := int64(cID)
|
||||
channelData := getSubscriptionChannelData(channelID)
|
||||
switch channelData.Subscription {
|
||||
case krakenWsTicker:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket ticker data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessTickers(&channelData, response[1].(map[string]interface{}))
|
||||
return k.wsProcessTickers(&channelData, response[1].(map[string]interface{}))
|
||||
case krakenWsOHLC:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessCandles(&channelData, response[1].([]interface{}))
|
||||
return k.wsProcessCandles(&channelData, response[1].([]interface{}))
|
||||
case krakenWsOrderbook:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{}))
|
||||
return k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{}))
|
||||
case krakenWsSpread:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket Spread data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessSpread(&channelData, response[1].([]interface{}))
|
||||
case krakenWsTrade:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket Trade data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessTrades(&channelData, response[1].([]interface{}))
|
||||
default:
|
||||
log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v",
|
||||
return fmt.Errorf("%s Unidentified websocket data received: %+v",
|
||||
k.Name,
|
||||
response)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleEventResponse classifies the WS response and sends to appropriate handler
|
||||
func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResponse []byte) {
|
||||
switch response.Event {
|
||||
case wshandler.Pong:
|
||||
break
|
||||
case krakenWsHeartbeat:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket heartbeat data received",
|
||||
k.Name)
|
||||
}
|
||||
case krakenWsSystemStatus:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket status data received",
|
||||
k.Name)
|
||||
}
|
||||
if response.Status != "online" {
|
||||
k.Websocket.DataHandler <- fmt.Errorf("%v Websocket status '%v'",
|
||||
k.Name, response.Status)
|
||||
}
|
||||
if response.WebsocketStatusResponse.Version > krakenWSSupportedVersion {
|
||||
log.Warnf(log.ExchangeSys, "%v New version of Websocket API released. Was %v Now %v",
|
||||
k.Name, krakenWSSupportedVersion, response.WebsocketStatusResponse.Version)
|
||||
}
|
||||
case krakenWsSubscriptionStatus:
|
||||
k.WebsocketConn.AddResponseWithID(response.RequestID, rawResponse)
|
||||
if response.Status != "subscribed" {
|
||||
k.Websocket.DataHandler <- fmt.Errorf("%v %v %v", k.Name, response.RequestID, response.WebsocketErrorResponse.ErrorMessage)
|
||||
return
|
||||
}
|
||||
addNewSubscriptionChannelData(response)
|
||||
default:
|
||||
log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v",
|
||||
k.Name, response)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) wsHandleAuthDataResponse(response WebsocketDataResponse) {
|
||||
func (k *Kraken) wsHandleAuthDataResponse(response WebsocketDataResponse) error {
|
||||
if chName, ok := response[1].(string); ok {
|
||||
switch chName {
|
||||
case krakenWsOwnTrades:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket auth own trade data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessOwnTrades(&response[0])
|
||||
return k.wsProcessOwnTrades(response[0])
|
||||
case krakenWsOpenOrders:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket auth open order data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessOpenOrders(&response[0])
|
||||
return k.wsProcessOpenOrders(response[0])
|
||||
default:
|
||||
return fmt.Errorf("%v Unidentified websocket data received: %+v",
|
||||
k.Name, response)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kraken) wsProcessOwnTrades(ownOrders interface{}) {
|
||||
func (k *Kraken) wsProcessOwnTrades(ownOrders interface{}) error {
|
||||
if data, ok := ownOrders.([]interface{}); ok {
|
||||
for i := range data {
|
||||
ownTrade := data[i].(map[string]interface{})
|
||||
for _, val := range ownTrade {
|
||||
tradeData := val.(map[string]interface{})
|
||||
cost, err := strconv.ParseFloat(tradeData["cost"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
fee, err := strconv.ParseFloat(tradeData["fee"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
margin, err := strconv.ParseFloat(tradeData["margin"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
vol, err := strconv.ParseFloat(tradeData["vol"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
price, err := strconv.ParseFloat(tradeData["price"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
timeTogether, err := strconv.ParseFloat(tradeData["time"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
first, second, err := convert.SplitFloatDecimals(timeTogether)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
k.Websocket.DataHandler <- WsOwnTrade{
|
||||
Cost: cost,
|
||||
Fee: fee,
|
||||
Margin: margin,
|
||||
OrderTransactionID: tradeData["ordertxid"].(string),
|
||||
OrderType: tradeData["ordertype"].(string),
|
||||
Pair: tradeData["pair"].(string),
|
||||
PostTransactionID: tradeData["postxid"].(string),
|
||||
Price: price,
|
||||
Time: time.Unix(first, second),
|
||||
Type: tradeData["type"].(string),
|
||||
Vol: vol,
|
||||
}
|
||||
trades, err := json.Marshal(data[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
k.Websocket.DataHandler <- errors.New(k.Name + " - Invalid own trades data")
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) wsProcessOpenOrders(ownOrders interface{}) {
|
||||
if data, ok := ownOrders.([]interface{}); ok {
|
||||
for i := range data {
|
||||
ownTrade := data[i].(map[string]interface{})
|
||||
for key, val := range ownTrade {
|
||||
tradeData := val.(map[string]interface{})
|
||||
if len(tradeData) == 1 {
|
||||
// just a status update
|
||||
if status, ok := tradeData["status"].(string); ok {
|
||||
k.Websocket.DataHandler <- k.Name + " - Order " + key + " " + status
|
||||
var result map[string]*WsOwnTrade
|
||||
err = json.Unmarshal(trades, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range result {
|
||||
oSide, err := order.StringToOrderSide(val.Type)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: k.Name,
|
||||
OrderID: key,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
startTimeConv, err := strconv.ParseFloat(tradeData["starttm"].(string), 64)
|
||||
oType, err := order.StringToOrderType(val.OrderType)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
k.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: k.Name,
|
||||
OrderID: key,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
startTime, startTimeNano, err := convert.SplitFloatDecimals(startTimeConv)
|
||||
txTime, txTimeNano, err := convert.SplitFloatDecimals(val.Time)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
openTimeConv, err := strconv.ParseFloat(tradeData["opentm"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
trade := order.TradeHistory{
|
||||
Price: val.Price,
|
||||
Amount: val.Vol,
|
||||
Fee: val.Fee,
|
||||
Exchange: k.Name,
|
||||
TID: key,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Timestamp: time.Unix(txTime, txTimeNano),
|
||||
}
|
||||
openTime, openTimeNano, err := convert.SplitFloatDecimals(openTimeConv)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
expireTimeConv, err := strconv.ParseFloat(tradeData["expiretm"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
expireTime, expireTimeNano, err := convert.SplitFloatDecimals(expireTimeConv)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
cost, err := strconv.ParseFloat(tradeData["cost"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
executedVolume, err := strconv.ParseFloat(tradeData["vol_exec"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
volume, err := strconv.ParseFloat(tradeData["vol"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
userReference, err := strconv.ParseFloat(tradeData["userref"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
stopPrice, err := strconv.ParseFloat(tradeData["stopprice"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
price, err := strconv.ParseFloat(tradeData["price"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
limitPrice, err := strconv.ParseFloat(tradeData["limitprice"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
fee, err := strconv.ParseFloat(tradeData["fee"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
descriptionSubData := tradeData["description"].(map[string]interface{})
|
||||
descriptionPrice, err := strconv.ParseFloat(descriptionSubData["price"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
descriptionPrice2, err := strconv.ParseFloat(descriptionSubData["price2"].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
}
|
||||
description := WsOpenOrderDescription{
|
||||
Close: descriptionSubData["close"].(string),
|
||||
Leverage: descriptionSubData["leverage"].(string),
|
||||
Order: descriptionSubData["order"].(string),
|
||||
OrderType: descriptionSubData["ordertype"].(string),
|
||||
Pair: descriptionSubData["pair"].(string),
|
||||
Price: descriptionPrice,
|
||||
Price2: descriptionPrice2,
|
||||
Type: descriptionSubData["type"].(string),
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- WsOpenOrders{
|
||||
Cost: cost,
|
||||
ExpireTime: time.Unix(expireTime, expireTimeNano),
|
||||
Description: description,
|
||||
Fee: fee,
|
||||
LimitPrice: limitPrice,
|
||||
Misc: tradeData["misc"].(string),
|
||||
OFlags: tradeData["oflags"].(string),
|
||||
OpenTime: time.Unix(openTime, openTimeNano),
|
||||
Price: price,
|
||||
RefID: tradeData["refid"].(string),
|
||||
StartTime: time.Unix(startTime, startTimeNano),
|
||||
Status: tradeData["status"].(string),
|
||||
StopPrice: stopPrice,
|
||||
UserReference: userReference,
|
||||
Volume: volume,
|
||||
ExecutedVolume: executedVolume,
|
||||
k.Websocket.DataHandler <- &order.Modify{
|
||||
Exchange: k.Name,
|
||||
ID: val.OrderTransactionID,
|
||||
Trades: []order.TradeHistory{trade},
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
k.Websocket.DataHandler <- errors.New(k.Name + " - Invalid own trades data")
|
||||
return nil
|
||||
}
|
||||
return errors.New(k.Name + " - Invalid own trades data")
|
||||
}
|
||||
|
||||
func (k *Kraken) wsProcessOpenOrders(ownOrders interface{}) error {
|
||||
if data, ok := ownOrders.([]interface{}); ok {
|
||||
for i := range data {
|
||||
orders, err := json.Marshal(data[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result map[string]*WsOpenOrder
|
||||
err = json.Unmarshal(orders, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range result {
|
||||
var oStatus order.Status
|
||||
oStatus, err = order.StringToOrderStatus(val.Status)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: k.Name,
|
||||
OrderID: key,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if val.Description.Price > 0 {
|
||||
startTime, startTimeNano, err := convert.SplitFloatDecimals(val.StartTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oSide, err := order.StringToOrderSide(val.Description.Type)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: k.Name,
|
||||
OrderID: key,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if strings.Contains(val.Description.Order, "sell") {
|
||||
oSide = order.Sell
|
||||
}
|
||||
oType, err := order.StringToOrderType(val.Description.Type)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: k.Name,
|
||||
OrderID: key,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
p := currency.NewPairFromString(val.Description.Pair)
|
||||
var a asset.Item
|
||||
a, err = k.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.Websocket.DataHandler <- &order.Modify{
|
||||
Leverage: val.Description.Leverage,
|
||||
Price: val.Price,
|
||||
Amount: val.Volume,
|
||||
LimitPriceUpper: val.LimitPrice,
|
||||
ExecutedAmount: val.ExecutedVolume,
|
||||
RemainingAmount: val.Volume - val.ExecutedVolume,
|
||||
Fee: val.Fee,
|
||||
Exchange: k.Name,
|
||||
ID: key,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: time.Unix(startTime, startTimeNano),
|
||||
Pair: p,
|
||||
}
|
||||
} else {
|
||||
k.Websocket.DataHandler <- &order.Modify{
|
||||
Exchange: k.Name,
|
||||
ID: key,
|
||||
Status: oStatus,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New(k.Name + " - Invalid own trades data")
|
||||
}
|
||||
|
||||
// addNewSubscriptionChannelData stores channel ids, pairs and subscription types to an array
|
||||
// allowing correlation between subscriptions and returned data
|
||||
func addNewSubscriptionChannelData(response *WebsocketEventResponse) {
|
||||
func (k *Kraken) addNewSubscriptionChannelData(response *wsSubscription) {
|
||||
// We change the / to - to maintain compatibility with REST/config
|
||||
pair := currency.NewPairWithDelimiter(response.Pair.Base.String(),
|
||||
response.Pair.Quote.String(), "-")
|
||||
var pair currency.Pair
|
||||
if response.Pair != "" {
|
||||
pair = currency.NewPairFromString(response.Pair)
|
||||
pair.Delimiter = k.CurrencyPairs.RequestFormat.Delimiter
|
||||
}
|
||||
subscriptionChannelPair = append(subscriptionChannelPair, WebsocketChannelData{
|
||||
Subscription: response.Subscription.Name,
|
||||
Pair: pair,
|
||||
@@ -454,47 +439,34 @@ func getSubscriptionChannelData(id int64) WebsocketChannelData {
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data map[string]interface{}) {
|
||||
func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data map[string]interface{}) error {
|
||||
closePrice, err := strconv.ParseFloat(data["c"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
openPrice, err := strconv.ParseFloat(data["o"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
highPrice, err := strconv.ParseFloat(data["h"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
lowPrice, err := strconv.ParseFloat(data["l"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
quantity, err := strconv.ParseFloat(data["v"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
ask, err := strconv.ParseFloat(data["a"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
bid, err := strconv.ParseFloat(data["b"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- &ticker.Price{
|
||||
@@ -509,9 +481,10 @@ func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data map[st
|
||||
AssetType: asset.Spot,
|
||||
Pair: channelData.Pair,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
// wsProcessSpread converts spread/orderbook data and sends it to the datahandler
|
||||
func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data []interface{}) {
|
||||
bestBid := data[0].(string)
|
||||
bestAsk := data[1].(string)
|
||||
@@ -561,7 +534,10 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
var tSide = order.Buy
|
||||
if trade[3].(string) == "s" {
|
||||
tSide = order.Sell
|
||||
}
|
||||
k.Websocket.DataHandler <- wshandler.TradeData{
|
||||
AssetType: asset.Spot,
|
||||
CurrencyPair: channelData.Pair,
|
||||
@@ -569,17 +545,20 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Timestamp: timeUnix,
|
||||
Side: trade[3].(string),
|
||||
Side: tSide,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessOrderBook determines if the orderbook data is partial or update
|
||||
// Then sends to appropriate fun
|
||||
func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[string]interface{}) {
|
||||
func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[string]interface{}) error {
|
||||
if fullAsk, ok := data["as"].([]interface{}); ok {
|
||||
fullBids := data["as"].([]interface{})
|
||||
k.wsProcessOrderBookPartial(channelData, fullAsk, fullBids)
|
||||
err := k.wsProcessOrderBookPartial(channelData, fullAsk, fullBids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
askData, asksExist := data["a"].([]interface{})
|
||||
bidData, bidsExist := data["b"].([]interface{})
|
||||
@@ -593,13 +572,15 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[
|
||||
Currency: channelData.Pair,
|
||||
}
|
||||
k.Websocket.ResubscribeToChannel(subscriptionToRemove)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsProcessOrderBookPartial creates a new orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, askData, bidData []interface{}) {
|
||||
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, askData, bidData []interface{}) error {
|
||||
base := orderbook.Base{
|
||||
Pair: channelData.Pair,
|
||||
AssetType: asset.Spot,
|
||||
@@ -612,13 +593,11 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as
|
||||
asks := askData[i].([]interface{})
|
||||
price, err := strconv.ParseFloat(asks[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
amount, err := strconv.ParseFloat(asks[1].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
base.Asks = append(base.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
@@ -626,8 +605,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as
|
||||
})
|
||||
timeData, err := strconv.ParseFloat(asks[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -640,13 +618,11 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as
|
||||
bids := bidData[i].([]interface{})
|
||||
price, err := strconv.ParseFloat(bids[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
amount, err := strconv.ParseFloat(bids[1].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
base.Bids = append(base.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
@@ -654,8 +630,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as
|
||||
})
|
||||
timeData, err := strconv.ParseFloat(bids[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
sec, dec := math.Modf(timeData)
|
||||
bidUpdateTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -667,14 +642,14 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as
|
||||
base.ExchangeName = k.Name
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&base)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: k.Name,
|
||||
Asset: asset.Spot,
|
||||
Pair: channelData.Pair,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
|
||||
@@ -757,51 +732,44 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask
|
||||
}
|
||||
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []interface{}) {
|
||||
func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []interface{}) error {
|
||||
startTime, err := strconv.ParseFloat(data[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
sec, dec := math.Modf(startTime)
|
||||
startTimeUnix := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
|
||||
endTime, err := strconv.ParseFloat(data[1].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
sec, dec = math.Modf(endTime)
|
||||
endTimeUnix := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
|
||||
openPrice, err := strconv.ParseFloat(data[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
highPrice, err := strconv.ParseFloat(data[3].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
lowPrice, err := strconv.ParseFloat(data[4].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
closePrice, err := strconv.ParseFloat(data[5].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
volume, err := strconv.ParseFloat(data[7].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.KlineData{
|
||||
@@ -819,6 +787,7 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []inte
|
||||
ClosePrice: closePrice,
|
||||
Volume: volume,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
|
||||
@@ -111,6 +111,8 @@ func (k *Kraken) SetDefaults() {
|
||||
SubmitOrder: true,
|
||||
CancelOrder: true,
|
||||
CancelOrders: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup |
|
||||
exchange.WithdrawCryptoWith2FA |
|
||||
@@ -436,8 +438,8 @@ func (k *Kraken) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
var resp string
|
||||
resp, err = k.wsAddOrder(&WsAddOrderRequest{
|
||||
OrderType: s.OrderType.String(),
|
||||
OrderSide: s.OrderSide.String(),
|
||||
OrderType: s.Type.String(),
|
||||
OrderSide: s.Side.String(),
|
||||
Pair: s.Pair.String(),
|
||||
Price: s.Price,
|
||||
Volume: s.Amount,
|
||||
@@ -450,8 +452,8 @@ func (k *Kraken) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
} else {
|
||||
var response AddOrderResponse
|
||||
response, err = k.AddOrder(s.Pair.String(),
|
||||
s.OrderSide.String(),
|
||||
s.OrderType.String(),
|
||||
s.Side.String(),
|
||||
s.Type.String(),
|
||||
s.Amount,
|
||||
s.Price,
|
||||
0,
|
||||
@@ -464,7 +466,7 @@ func (k *Kraken) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
submitOrderResponse.OrderID = strings.Join(response.TransactionIds, ", ")
|
||||
}
|
||||
}
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
@@ -480,9 +482,9 @@ func (k *Kraken) ModifyOrder(action *order.Modify) (string, error) {
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (k *Kraken) CancelOrder(order *order.Cancel) error {
|
||||
if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
return k.wsCancelOrders([]string{order.OrderID})
|
||||
return k.wsCancelOrders([]string{order.ID})
|
||||
}
|
||||
_, err := k.CancelExistingOrder(order.OrderID)
|
||||
_, err := k.CancelExistingOrder(order.ID)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -547,10 +549,10 @@ func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
orderDetail = order.Detail{
|
||||
Exchange: k.Name,
|
||||
ID: orderID,
|
||||
CurrencyPair: currency.NewPairFromString(orderInfo.Description.Pair),
|
||||
OrderSide: side,
|
||||
OrderType: oType,
|
||||
OrderDate: time.Unix(firstNum, decNum),
|
||||
Pair: currency.NewPairFromString(orderInfo.Description.Pair),
|
||||
Side: side,
|
||||
Type: oType,
|
||||
Date: time.Unix(firstNum, decNum),
|
||||
Status: status,
|
||||
Price: orderInfo.Price,
|
||||
Amount: orderInfo.Volume,
|
||||
@@ -655,17 +657,17 @@ func (k *Kraken) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
RemainingAmount: (resp.Open[i].Volume - resp.Open[i].VolumeExecuted),
|
||||
ExecutedAmount: resp.Open[i].VolumeExecuted,
|
||||
Exchange: k.Name,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
Price: resp.Open[i].Description.Price,
|
||||
OrderSide: side,
|
||||
OrderType: orderType,
|
||||
CurrencyPair: symbol,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -698,16 +700,16 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]or
|
||||
RemainingAmount: (resp.Closed[i].Volume - resp.Closed[i].VolumeExecuted),
|
||||
ExecutedAmount: resp.Closed[i].VolumeExecuted,
|
||||
Exchange: k.Name,
|
||||
OrderDate: orderDate,
|
||||
Date: orderDate,
|
||||
Price: resp.Closed[i].Description.Price,
|
||||
OrderSide: side,
|
||||
OrderType: orderType,
|
||||
CurrencyPair: symbol,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
Pair: symbol,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
|
||||
order.FilterOrdersByCurrencies(&orders, getOrdersRequest.Currencies)
|
||||
order.FilterOrdersBySide(&orders, getOrdersRequest.Side)
|
||||
order.FilterOrdersByCurrencies(&orders, getOrdersRequest.Pairs)
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := l.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -276,7 +276,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := l.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -303,11 +303,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.EUR,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := l.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -324,10 +324,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
err := l.CancelOrder(orderCancellation)
|
||||
@@ -346,10 +346,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
Pair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := l.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -147,15 +147,22 @@ func (l *LakeBTC) processTrades(data, channel string) error {
|
||||
}
|
||||
curr := l.getCurrencyFromChannel(channel)
|
||||
for i := range tradeData.Trades {
|
||||
tSide, err := order.StringToOrderSide(tradeData.Trades[i].Type)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: l.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
l.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(tradeData.Trades[i].Date, 0),
|
||||
CurrencyPair: curr,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: l.Name,
|
||||
EventType: asset.Spot.String(),
|
||||
EventType: order.UnknownType,
|
||||
Price: tradeData.Trades[i].Price,
|
||||
Amount: tradeData.Trades[i].Amount,
|
||||
Side: tradeData.Trades[i].Type,
|
||||
Side: tSide,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -347,7 +347,7 @@ func (l *LakeBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
isBuyOrder := s.OrderSide == order.Buy
|
||||
isBuyOrder := s.Side == order.Buy
|
||||
response, err := l.Trade(isBuyOrder, s.Amount, s.Price,
|
||||
s.Pair.Lower().String())
|
||||
if err != nil {
|
||||
@@ -358,7 +358,7 @@ func (l *LakeBTC) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
}
|
||||
return submitOrderResponse, nil
|
||||
@@ -372,7 +372,7 @@ func (l *LakeBTC) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (l *LakeBTC) CancelOrder(order *order.Cancel) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orderIDInt, err := strconv.ParseInt(order.ID, 10, 64)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -476,19 +476,19 @@ func (l *LakeBTC) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
side := order.Side(strings.ToUpper(resp[i].Type))
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].Amount,
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
OrderSide: side,
|
||||
OrderDate: orderDate,
|
||||
CurrencyPair: symbol,
|
||||
Exchange: l.Name,
|
||||
Amount: resp[i].Amount,
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
Side: side,
|
||||
Date: orderDate,
|
||||
Pair: symbol,
|
||||
Exchange: l.Name,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
@@ -513,19 +513,19 @@ func (l *LakeBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
side := order.Side(strings.ToUpper(resp[i].Type))
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].Amount,
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
OrderSide: side,
|
||||
OrderDate: orderDate,
|
||||
CurrencyPair: symbol,
|
||||
Exchange: l.Name,
|
||||
Amount: resp[i].Amount,
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Price: resp[i].Price,
|
||||
Side: side,
|
||||
Date: orderDate,
|
||||
Pair: symbol,
|
||||
Exchange: l.Name,
|
||||
})
|
||||
}
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, req.StartTicks, req.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, req.OrderSide)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Currencies)
|
||||
order.FilterOrdersBySide(&orders, req.Side)
|
||||
order.FilterOrdersByCurrencies(&orders, req.Pairs)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -316,11 +316,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Quote: currency.USDT,
|
||||
Delimiter: "_",
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := l.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -337,8 +337,8 @@ func TestCancelOrder(t *testing.T) {
|
||||
}
|
||||
cp := currency.NewPairWithDelimiter(currency.ETH.String(), currency.BTC.String(), "_")
|
||||
var a order.Cancel
|
||||
a.CurrencyPair = cp
|
||||
a.OrderID = "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23"
|
||||
a.Pair = cp
|
||||
a.ID = "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23"
|
||||
err := l.CancelOrder(&a)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -400,7 +400,7 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
t.Skip("API keys required but not set, skipping test")
|
||||
}
|
||||
var input order.GetOrdersRequest
|
||||
input.OrderSide = order.Buy
|
||||
input.Side = order.Buy
|
||||
_, err := l.GetOrderHistory(&input)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
||||
@@ -316,14 +316,14 @@ func (l *Lbank) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if s.OrderSide != order.Buy && s.OrderSide != order.Sell {
|
||||
if s.Side != order.Buy && s.Side != order.Sell {
|
||||
return resp,
|
||||
fmt.Errorf("%s order side is not supported by the exchange",
|
||||
s.OrderSide)
|
||||
s.Side)
|
||||
}
|
||||
tempResp, err := l.CreateOrder(
|
||||
l.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
s.OrderSide.String(),
|
||||
s.Side.String(),
|
||||
s.Amount,
|
||||
s.Price)
|
||||
if err != nil {
|
||||
@@ -331,7 +331,7 @@ func (l *Lbank) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
resp.IsOrderPlaced = true
|
||||
resp.OrderID = tempResp.OrderID
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
resp.FullyMatched = true
|
||||
}
|
||||
return resp, nil
|
||||
@@ -345,8 +345,8 @@ func (l *Lbank) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (l *Lbank) CancelOrder(order *order.Cancel) error {
|
||||
_, err := l.RemoveOrder(l.FormatExchangeCurrency(order.CurrencyPair,
|
||||
order.AssetType).String(), order.OrderID)
|
||||
_, err := l.RemoveOrder(l.FormatExchangeCurrency(order.Pair,
|
||||
order.AssetType).String(), order.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ func (l *Lbank) CancelAllOrders(orders *order.Cancel) (order.CancelAllResponse,
|
||||
}
|
||||
|
||||
for key := range orderIDs {
|
||||
if key != orders.CurrencyPair.String() {
|
||||
if key != orders.Pair.String() {
|
||||
continue
|
||||
}
|
||||
var x, y = 0, 0
|
||||
@@ -425,11 +425,11 @@ func (l *Lbank) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
return resp, err
|
||||
}
|
||||
resp.Exchange = l.Name
|
||||
resp.CurrencyPair = currency.NewPairFromString(key)
|
||||
resp.Pair = currency.NewPairFromString(key)
|
||||
if strings.EqualFold(tempResp.Orders[0].Type, order.Buy.String()) {
|
||||
resp.OrderSide = order.Buy
|
||||
resp.Side = order.Buy
|
||||
} else {
|
||||
resp.OrderSide = order.Sell
|
||||
resp.Side = order.Sell
|
||||
}
|
||||
z := tempResp.Orders[0].Status
|
||||
switch {
|
||||
@@ -514,11 +514,11 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]ord
|
||||
return finalResp, err
|
||||
}
|
||||
resp.Exchange = l.Name
|
||||
resp.CurrencyPair = currency.NewPairFromString(key)
|
||||
resp.Pair = currency.NewPairFromString(key)
|
||||
if strings.EqualFold(tempResp.Orders[0].Type, order.Buy.String()) {
|
||||
resp.OrderSide = order.Buy
|
||||
resp.Side = order.Buy
|
||||
} else {
|
||||
resp.OrderSide = order.Sell
|
||||
resp.Side = order.Sell
|
||||
}
|
||||
z := tempResp.Orders[0].Status
|
||||
switch {
|
||||
@@ -537,7 +537,7 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]ord
|
||||
}
|
||||
resp.Price = tempResp.Orders[0].Price
|
||||
resp.Amount = tempResp.Orders[0].Amount
|
||||
resp.OrderDate = time.Unix(tempResp.Orders[0].CreateTime, 9)
|
||||
resp.Date = time.Unix(tempResp.Orders[0].CreateTime, 9)
|
||||
resp.ExecutedAmount = tempResp.Orders[0].DealAmount
|
||||
resp.RemainingAmount = tempResp.Orders[0].Amount - tempResp.Orders[0].DealAmount
|
||||
resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{
|
||||
@@ -547,15 +547,15 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]ord
|
||||
if err != nil {
|
||||
resp.Fee = lbankFeeNotFound
|
||||
}
|
||||
for y := int(0); y < len(getOrdersRequest.Currencies); y++ {
|
||||
if getOrdersRequest.Currencies[y].String() != key {
|
||||
for y := int(0); y < len(getOrdersRequest.Pairs); y++ {
|
||||
if getOrdersRequest.Pairs[y].String() != key {
|
||||
continue
|
||||
}
|
||||
if getOrdersRequest.OrderSide == "ANY" {
|
||||
if getOrdersRequest.Side == "ANY" {
|
||||
finalResp = append(finalResp, resp)
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(getOrdersRequest.OrderSide.String(),
|
||||
if strings.EqualFold(getOrdersRequest.Side.String(),
|
||||
tempResp.Orders[0].Type) {
|
||||
finalResp = append(finalResp, resp)
|
||||
}
|
||||
@@ -571,10 +571,10 @@ func (l *Lbank) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]ord
|
||||
var finalResp []order.Detail
|
||||
var resp order.Detail
|
||||
var tempCurr currency.Pairs
|
||||
if len(getOrdersRequest.Currencies) == 0 {
|
||||
if len(getOrdersRequest.Pairs) == 0 {
|
||||
tempCurr = l.GetEnabledPairs(asset.Spot)
|
||||
} else {
|
||||
tempCurr = getOrdersRequest.Currencies
|
||||
tempCurr = getOrdersRequest.Pairs
|
||||
}
|
||||
for a := range tempCurr {
|
||||
p := l.FormatExchangeCurrency(tempCurr[a], asset.Spot).String()
|
||||
@@ -590,11 +590,11 @@ func (l *Lbank) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]ord
|
||||
}
|
||||
for x := 0; x < len(tempResp.Orders); x++ {
|
||||
resp.Exchange = l.Name
|
||||
resp.CurrencyPair = currency.NewPairFromString(tempResp.Orders[x].Symbol)
|
||||
resp.Pair = currency.NewPairFromString(tempResp.Orders[x].Symbol)
|
||||
if strings.EqualFold(tempResp.Orders[x].Type, order.Buy.String()) {
|
||||
resp.OrderSide = order.Buy
|
||||
resp.Side = order.Buy
|
||||
} else {
|
||||
resp.OrderSide = order.Sell
|
||||
resp.Side = order.Sell
|
||||
}
|
||||
z := tempResp.Orders[x].Status
|
||||
switch {
|
||||
@@ -613,7 +613,7 @@ func (l *Lbank) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]ord
|
||||
}
|
||||
resp.Price = tempResp.Orders[x].Price
|
||||
resp.Amount = tempResp.Orders[x].Amount
|
||||
resp.OrderDate = time.Unix(tempResp.Orders[x].CreateTime, 9)
|
||||
resp.Date = time.Unix(tempResp.Orders[x].CreateTime, 9)
|
||||
resp.ExecutedAmount = tempResp.Orders[x].DealAmount
|
||||
resp.RemainingAmount = tempResp.Orders[x].Price - tempResp.Orders[x].DealAmount
|
||||
resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{
|
||||
|
||||
@@ -203,7 +203,7 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := l.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -221,7 +221,7 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
_, err := l.GetOrderHistory(&getOrdersRequest)
|
||||
@@ -253,11 +253,11 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Base: currency.BTC,
|
||||
Quote: currency.EUR,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := l.SubmitOrder(orderSubmission)
|
||||
switch {
|
||||
@@ -277,10 +277,10 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
}
|
||||
|
||||
err := l.CancelOrder(orderCancellation)
|
||||
@@ -301,10 +301,10 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
ID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
CurrencyPair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
}
|
||||
|
||||
resp, err := l.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -309,7 +309,7 @@ func (l *LocalBitcoins) SubmitOrder(s *order.Submit) (order.SubmitResponse, erro
|
||||
Currency: s.Pair.Quote.String(),
|
||||
AccountInfo: "-",
|
||||
BankName: "Bank",
|
||||
MSG: s.OrderSide.String(),
|
||||
MSG: s.Side.String(),
|
||||
SMSVerficationRequired: true,
|
||||
TrackMaxAmount: true,
|
||||
RequireTrustedByAdvertiser: true,
|
||||
@@ -368,7 +368,7 @@ func (l *LocalBitcoins) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (l *LocalBitcoins) CancelOrder(order *order.Cancel) error {
|
||||
return l.DeleteAd(order.OrderID)
|
||||
return l.DeleteAd(order.ID)
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
@@ -472,13 +472,13 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].Data.AmountBTC,
|
||||
Price: resp[i].Data.Amount,
|
||||
ID: strconv.FormatInt(int64(resp[i].Data.Advertisement.ID), 10),
|
||||
OrderDate: orderDate,
|
||||
Fee: resp[i].Data.FeeBTC,
|
||||
OrderSide: side,
|
||||
CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
Amount: resp[i].Data.AmountBTC,
|
||||
Price: resp[i].Data.Amount,
|
||||
ID: strconv.FormatInt(int64(resp[i].Data.Advertisement.ID), 10),
|
||||
Date: orderDate,
|
||||
Fee: resp[i].Data.FeeBTC,
|
||||
Side: side,
|
||||
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
resp[i].Data.Currency,
|
||||
l.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
Exchange: l.Name,
|
||||
@@ -487,7 +487,7 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks,
|
||||
getOrdersRequest.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, getOrdersRequest.Side)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
@@ -548,14 +548,14 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: allTrades[i].Data.AmountBTC,
|
||||
Price: allTrades[i].Data.Amount,
|
||||
ID: strconv.FormatInt(int64(allTrades[i].Data.Advertisement.ID), 10),
|
||||
OrderDate: orderDate,
|
||||
Fee: allTrades[i].Data.FeeBTC,
|
||||
OrderSide: side,
|
||||
Status: order.Status(status),
|
||||
CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
Amount: allTrades[i].Data.AmountBTC,
|
||||
Price: allTrades[i].Data.Amount,
|
||||
ID: strconv.FormatInt(int64(allTrades[i].Data.Advertisement.ID), 10),
|
||||
Date: orderDate,
|
||||
Fee: allTrades[i].Data.FeeBTC,
|
||||
Side: side,
|
||||
Status: order.Status(status),
|
||||
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
allTrades[i].Data.Currency,
|
||||
l.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
Exchange: l.Name,
|
||||
@@ -564,7 +564,7 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest
|
||||
|
||||
order.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks,
|
||||
getOrdersRequest.EndTicks)
|
||||
order.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
|
||||
order.FilterOrdersBySide(&orders, getOrdersRequest.Side)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func RegisterHandler(pattern string, mock map[string][]HTTPResponse, mux *http.S
|
||||
MessageWriteJSON(w, http.StatusOK, payload)
|
||||
return
|
||||
|
||||
case http.MethodPost:
|
||||
case http.MethodPost, http.MethodPut:
|
||||
switch r.Header.Get(contentType) {
|
||||
case applicationURLEncoded:
|
||||
readBody, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -103,6 +103,9 @@ func (o *OKCoin) SetDefaults() {
|
||||
Unsubscribe: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
MessageCorrelation: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
AccountBalance: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -137,6 +137,9 @@ func (o *OKEX) SetDefaults() {
|
||||
Unsubscribe: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
MessageCorrelation: true,
|
||||
GetOrders: true,
|
||||
GetOrder: true,
|
||||
AccountBalance: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
|
||||
@@ -1306,93 +1306,74 @@ type WebsocketEventResponse struct {
|
||||
|
||||
// WebsocketDataResponse formats all response data for a websocket event
|
||||
type WebsocketDataResponse struct {
|
||||
Table string `json:"table"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Data []WebsocketDataWrapper `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketDataWrapper holds all data responses for websocket
|
||||
// Can review in future if struct becomes too large
|
||||
// allows for easy data processing
|
||||
type WebsocketDataWrapper struct {
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
WebsocketTickerData
|
||||
WebsocketCandleResponse
|
||||
WebsocketOrderBooksData
|
||||
WebsocketTradeResponse
|
||||
WebsocketFundingFeeResponse
|
||||
WebsocketMarkPriceResponse
|
||||
WebsocketEstimatedPriceResponse
|
||||
WebsocketPriceRangeResponse
|
||||
WebsocketUserSwapPositionResponse
|
||||
WebsocketUserSwapOrdersResponse
|
||||
WebsocketUserSwapFutureAccountResponse
|
||||
WebsocketUserSpotAccountResponse
|
||||
WebsocketSpotMarginOrderResponse
|
||||
WebsocketUserFutureFixedMarginAccountResponse
|
||||
WebsocketUserFuturePositionResponse
|
||||
WebsocketSpotOrderResponse
|
||||
Table string `json:"table"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Data []interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketTickerData contains formatted data for ticker related websocket responses
|
||||
type WebsocketTickerData struct {
|
||||
BaseVolume24h float64 `json:"base_volume_24h,string,omitempty"`
|
||||
BestAsk float64 `json:"best_ask,string,omitempty"`
|
||||
BestBid float64 `json:"best_bid,string,omitempty"`
|
||||
High24h float64 `json:"high_24h,string,omitempty"`
|
||||
Last float64 `json:"last,string,omitempty"`
|
||||
Low24h float64 `json:"low_24h,string,omitempty"`
|
||||
Open24h float64 `json:"open_24h,string,omitempty"`
|
||||
QuoteVolume24h float64 `json:"quote_volume_24h,string,omitempty"`
|
||||
Table string `json:"table"`
|
||||
Data []struct {
|
||||
BaseVolume24h float64 `json:"base_volume_24h,string"`
|
||||
BestAsk float64 `json:"best_ask,string"`
|
||||
BestAskSize float64 `json:"best_ask_size,string"`
|
||||
BestBid float64 `json:"best_bid,string"`
|
||||
BestBidSize float64 `json:"best_bid_size,string"`
|
||||
High24h float64 `json:"high_24h,string"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
LastQty float64 `json:"last_qty,string"`
|
||||
Low24h float64 `json:"low_24h,string"`
|
||||
Open24h float64 `json:"open_24h,string"`
|
||||
QuoteVolume24h float64 `json:"quote_volume_24h,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketTradeResponse contains formatted data for trade related websocket responses
|
||||
type WebsocketTradeResponse struct {
|
||||
Price float64 `json:"price,string,omitempty"`
|
||||
Side string `json:"side,omitempty"`
|
||||
Qty float64 `json:"qty,string,omitempty"`
|
||||
TradeID string `json:"trade_id,omitempty"`
|
||||
Table string `json:"table"`
|
||||
Data []struct {
|
||||
Price float64 `json:"price,string"`
|
||||
Size float64 `json:"size,string"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
Side string `json:"side"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
TradeID string `json:"trade_id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketCandleResponse contains formatted data for candle related websocket responses
|
||||
type WebsocketCandleResponse struct {
|
||||
Candle []string `json:"candle,omitempty"` // [0]timestamp, [1]open, [2]high, [3]low, [4]close, [5]volume, [6]currencyVolume
|
||||
Table string `json:"table"`
|
||||
Data []struct {
|
||||
Candle []string `json:"candle"` // [0]timestamp, [1]open, [2]high, [3]low, [4]close, [5]volume, [6]currencyVolume
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketFundingFeeResponse contains formatted data for funding fee related websocket responses
|
||||
type WebsocketFundingFeeResponse struct {
|
||||
FundingRate float64 `json:"funding_rate,string,omitempty"`
|
||||
FundingTime time.Time `json:"funding_time,omitempty"`
|
||||
InterestRate float64 `json:"interest_rate,string,omitempty"`
|
||||
}
|
||||
|
||||
// WebsocketMarkPriceResponse contains formatted data for mark prices
|
||||
type WebsocketMarkPriceResponse struct {
|
||||
MarkPrice float64 `json:"mark_price,string,omitempty"`
|
||||
}
|
||||
|
||||
// WebsocketEstimatedPriceResponse contains formatted data for estimated prices
|
||||
type WebsocketEstimatedPriceResponse struct {
|
||||
SettlementPrice float64 `json:"settlement_price,string,omitempty"`
|
||||
}
|
||||
|
||||
// WebsocketPriceRangeResponse contains formatted data for mark prices
|
||||
type WebsocketPriceRangeResponse struct {
|
||||
Highest float64 `json:"highest,omitempty"`
|
||||
Lowest float64 `json:"lowest,omitempty"`
|
||||
}
|
||||
|
||||
// WebsocketOrderBooksData contains orderbook data from WebsocketOrderBooksResponse
|
||||
// WebsocketOrderBooksData is the full websocket response containing orderbook data
|
||||
type WebsocketOrderBooksData struct {
|
||||
Asks [][]interface{} `json:"asks,omitempty"` // [0] Price, [1] Size, [2] Number of orders
|
||||
Bids [][]interface{} `json:"bids,omitempty"` // [0] Price, [1] Size, [2] Number of orders
|
||||
Checksum int32 `json:"checksum,omitempty"`
|
||||
Table string `json:"table"`
|
||||
Action string `json:"action"`
|
||||
Data []WebsocketOrderBook `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketOrderBook holds orderbook data
|
||||
type WebsocketOrderBook struct {
|
||||
Checksum int32 `json:"checksum,omitempty"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Asks [][]interface{} `json:"asks,omitempty"` // [0] Price, [1] Size, [2] Number of orders
|
||||
Bids [][]interface{} `json:"bids,omitempty"` // [0] Price, [1] Size, [2] Number of orders
|
||||
}
|
||||
|
||||
// WebsocketUserSwapPositionResponse contains formatted data for user position data
|
||||
type WebsocketUserSwapPositionResponse struct {
|
||||
Holding []WebsocketUserSwapPositionHoldingData `json:"holding,omitempty"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Holding []WebsocketUserSwapPositionHoldingData `json:"holding,omitempty"`
|
||||
}
|
||||
|
||||
// WebsocketUserSwapPositionHoldingData contains formatted data for user position holding data
|
||||
@@ -1409,110 +1390,30 @@ type WebsocketUserSwapPositionHoldingData struct {
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
}
|
||||
|
||||
// WebsocketUserSwapFutureAccountResponse contains formatted data for user account data
|
||||
type WebsocketUserSwapFutureAccountResponse struct {
|
||||
Equity float64 `json:"equity,string,omitempty"`
|
||||
FixedBalance float64 `json:"fixed_balance,string,omitempty"`
|
||||
MarginFrozen float64 `json:"margin_frozen,string,omitempty"`
|
||||
MarginRatio float64 `json:"margin_ratio,string,omitempty"`
|
||||
RealizedPnl float64 `json:"realized_pnl,string,omitempty"`
|
||||
UnrealizedPnl float64 `json:"unrealized_pnl,string,omitempty"`
|
||||
// MarginMode A member, but part already exists as part of WebsocketDataResponse
|
||||
// TotalAvailBalance A member, but part already exists as part of WebsocketDataResponse
|
||||
// Margin A member, but part already exists as part of WebsocketDataResponse
|
||||
}
|
||||
|
||||
// WebsocketUserSpotAccountResponse contains formatted data for user account data
|
||||
type WebsocketUserSpotAccountResponse struct {
|
||||
Balance string `json:"balance"`
|
||||
Available string `json:"available"`
|
||||
Currency string `json:"currency"`
|
||||
ID int64 `json:"id"`
|
||||
Hold string `json:"hold"`
|
||||
}
|
||||
|
||||
// WebsocketSpotMarginOrderResponse contains formatted data for user account data
|
||||
type WebsocketSpotMarginOrderResponse struct {
|
||||
MarginMode string `json:"margin_mode"`
|
||||
TotalAvailBalance string `json:"total_avail_balance"`
|
||||
// UnrealizedPnl A member, but part already exists as part of WebsocketDataResponse
|
||||
// Equity A member, but part already exists as part of WebsocketDataResponse
|
||||
// FixedBalance A member, but part already exists as part of WebsocketDataResponse
|
||||
// InstrumentID A member, but part already exists as part of WebsocketDataResponse
|
||||
// Margin A member, but part already exists as part of WebsocketDataResponse
|
||||
// MarginFrozen A member, but part already exists as part of WebsocketDataResponse
|
||||
// MarginRatio A member, but part already exists as part of WebsocketDataResponse
|
||||
// RealizedPnl A member, but part already exists as part of WebsocketDataResponse
|
||||
// Timestamp A member, but part already exists as part of WebsocketDataResponse
|
||||
}
|
||||
|
||||
// WebsocketUserFutureFixedMarginAccountResponse contains formatted data for user account data
|
||||
type WebsocketUserFutureFixedMarginAccountResponse map[string]WebsocketUserFutureFixedMarginAccountData
|
||||
|
||||
// WebsocketUserFutureFixedMarginAccountData contains the user fixed margin account data
|
||||
type WebsocketUserFutureFixedMarginAccountData struct {
|
||||
Contracts []WebsocketUserSwapFutureAccountResponse `json:"contracts"`
|
||||
Equity string `json:"equity"`
|
||||
MarginMode string `json:"margin_mode"`
|
||||
TotalAvailBalance string `json:"total_avail_balance"`
|
||||
}
|
||||
|
||||
// WebsocketUserSwapOrdersResponse contains formatted data for user order data
|
||||
type WebsocketUserSwapOrdersResponse struct {
|
||||
FilledQuantity float64 `json:"filled_qty,string,omitempty"`
|
||||
ClientOID string `json:"client_oid,omitempty"`
|
||||
Fee float64 `json:"fee,string,omitempty"`
|
||||
ContractValue float64 `json:"contract_val,string,omitempty"`
|
||||
PriceAverage float64 `json:"price_avg,string,omitempty"`
|
||||
OrderID string `json:"order_id,omitempty"`
|
||||
// Size A member, but part already exists as part of WebsocketDataResponse
|
||||
// Status A member, but part already exists as part of WebsocketDataResponse
|
||||
// Leverage A member, but part already exists as part of WebsocketDataResponse
|
||||
// Price A member, but part already exists as part of WebsocketDataResponse
|
||||
// Type A member, but part already exists as part of WebsocketDataResponse
|
||||
}
|
||||
|
||||
// WebsocketUserFuturePositionResponse contains formatted data for futures positions data
|
||||
type WebsocketUserFuturePositionResponse struct {
|
||||
LongQty string `json:"long_qty"`
|
||||
LongAvailQty int64 `json:"long_avail_qty"`
|
||||
LongAvgCost string `json:"long_avg_cost"`
|
||||
LongSettlementPrice string `json:"long_settlement_price"`
|
||||
RealisedPnl string `json:"realised_pnl"`
|
||||
ShortQty string `json:"short_qty"`
|
||||
ShortAvailQty string `json:"short_avail_qty"`
|
||||
ShortAvgCost string `json:"short_avg_cost"`
|
||||
ShortSettlementPrice string `json:"short_settlement_price"`
|
||||
LiquidationPrice string `json:"liquidation_price"`
|
||||
Leverage string `json:"leverage"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
LongMargin string `json:"long_margin"`
|
||||
LongLiquiPrice string `json:"long_liqui_price"`
|
||||
LongPnlRatio string `json:"long_pnl_ratio"`
|
||||
ShortMargin string `json:"short_margin"`
|
||||
ShortLiquiPrice string `json:"short_liqui_price"`
|
||||
ShortPnlRatio string `json:"short_pnl_ratio"`
|
||||
LongLeverage string `json:"long_leverage"`
|
||||
ShortLeverage string `json:"short_leverage"`
|
||||
// UpdatedAt A member, but part already exists as part of WebsocketDataResponse
|
||||
// MarginMode A member, but part already exists as part of WebsocketDataResponse
|
||||
// InstrumentID A member, but part already exists as part of WebsocketDataResponse
|
||||
}
|
||||
|
||||
// WebsocketSpotOrderResponse contains formatted data for spot user orders
|
||||
type WebsocketSpotOrderResponse struct {
|
||||
FilledNotional float64 `json:"filled_notional,string"`
|
||||
FilledSize float64 `json:"filled_size,string"`
|
||||
Notional float64 `json:"notional,string"`
|
||||
Size float64 `json:"size,string"`
|
||||
Status string `json:"status"`
|
||||
MarginTrading int64 `json:"margin_trading,omitempty"`
|
||||
Type string `json:"type"`
|
||||
// Price A member, but part already exists as part of WebsocketDataResponse
|
||||
// InstrumentID A member, but part already exists as part of WebsocketDataResponse
|
||||
// Timestamp A member, but part already exists as part of WebsocketDataResponse
|
||||
// OrderID A member, but part already exists as part of WebsocketDataResponse
|
||||
Table string `json:"table"`
|
||||
Data []struct {
|
||||
ClientOid string `json:"client_oid"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
FilledNotional float64 `json:"filled_notional,string"`
|
||||
FilledSize float64 `json:"filled_size,string"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
LastFillPx float64 `json:"last_fill_px,string"`
|
||||
LastFillQty float64 `json:"last_fill_qty,string"`
|
||||
LastFillTime time.Time `json:"last_fill_time"`
|
||||
MarginTrading int64 `json:"margin_trading,string"`
|
||||
Notional string `json:"notional"`
|
||||
OrderID string `json:"order_id"`
|
||||
OrderType int64 `json:"order_type,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
Side string `json:"side"`
|
||||
Size float64 `json:"size,string"`
|
||||
State int64 `json:"state,string"`
|
||||
Status string `json:"status"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketErrorResponse yo
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -188,7 +189,7 @@ func (o *OKGroup) WsConnect() error {
|
||||
}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go o.WsHandleData(&wg)
|
||||
go o.WsReadData(&wg)
|
||||
if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
err = o.WsLogin()
|
||||
if err != nil {
|
||||
@@ -205,66 +206,6 @@ func (o *OKGroup) WsConnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleData handles the read data from the websocket connection
|
||||
func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) {
|
||||
o.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
o.Websocket.Wg.Done()
|
||||
}()
|
||||
|
||||
wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
default:
|
||||
resp, err := o.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
o.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
o.Websocket.TrafficAlert <- struct{}{}
|
||||
var dataResponse WebsocketDataResponse
|
||||
err = json.Unmarshal(resp.Raw, &dataResponse)
|
||||
if err == nil && dataResponse.Table != "" {
|
||||
if len(dataResponse.Data) > 0 {
|
||||
o.WsHandleDataResponse(&dataResponse)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var errorResponse WebsocketErrorResponse
|
||||
err = json.Unmarshal(resp.Raw, &errorResponse)
|
||||
if err == nil && errorResponse.ErrorCode > 0 {
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"WS Error Event: %v Message: %v for %s",
|
||||
errorResponse.Event,
|
||||
errorResponse.Message,
|
||||
o.Name)
|
||||
}
|
||||
o.WsHandleErrorResponse(errorResponse)
|
||||
continue
|
||||
}
|
||||
var eventResponse WebsocketEventResponse
|
||||
err = json.Unmarshal(resp.Raw, &eventResponse)
|
||||
if err == nil && eventResponse.Event != "" {
|
||||
if eventResponse.Event == "login" {
|
||||
o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success)
|
||||
}
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"WS Event: %v on Channel: %v for %s",
|
||||
eventResponse.Event,
|
||||
eventResponse.Channel,
|
||||
o.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WsLogin sends a login request to websocket to enable access to authenticated endpoints
|
||||
func (o *OKGroup) WsLogin() error {
|
||||
o.Websocket.SetCanUseAuthenticatedEndpoints(true)
|
||||
@@ -292,113 +233,166 @@ func (o *OKGroup) WsLogin() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleErrorResponse sends an error message to ws handler
|
||||
func (o *OKGroup) WsHandleErrorResponse(event WebsocketErrorResponse) {
|
||||
errorMessage := fmt.Sprintf("%v error - %v message: %s ",
|
||||
o.Name,
|
||||
event.ErrorCode,
|
||||
event.Message)
|
||||
if o.Verbose {
|
||||
log.Error(log.ExchangeSys, errorMessage)
|
||||
}
|
||||
o.Websocket.DataHandler <- fmt.Errorf(errorMessage)
|
||||
}
|
||||
// WsReadData receives and passes on websocket messages for processing
|
||||
func (o *OKGroup) WsReadData(wg *sync.WaitGroup) {
|
||||
o.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
o.Websocket.Wg.Done()
|
||||
}()
|
||||
wg.Done()
|
||||
|
||||
// GetWsChannelWithoutOrderType takes WebsocketDataResponse.Table and returns
|
||||
// The base channel name eg receive "spot/depth5:BTC-USDT" return "depth5"
|
||||
func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string {
|
||||
index := strings.Index(table, "/")
|
||||
if index == -1 {
|
||||
return table
|
||||
}
|
||||
channel := table[index+1:]
|
||||
index = strings.Index(channel, ":")
|
||||
// Some events do not contain a currency
|
||||
if index == -1 {
|
||||
return channel
|
||||
}
|
||||
|
||||
return channel[:index]
|
||||
}
|
||||
|
||||
// GetAssetTypeFromTableName gets the asset type from the table name
|
||||
// eg "spot/ticker:BTCUSD" results in "SPOT"
|
||||
func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item {
|
||||
assetIndex := strings.Index(table, "/")
|
||||
switch table[:assetIndex] {
|
||||
case asset.Futures.String():
|
||||
return asset.Futures
|
||||
case asset.Spot.String():
|
||||
return asset.Spot
|
||||
case "swap":
|
||||
return asset.PerpetualSwap
|
||||
case asset.Index.String():
|
||||
return asset.Index
|
||||
default:
|
||||
log.Warnf(log.ExchangeSys, "%s unhandled asset type %s",
|
||||
o.Name,
|
||||
table[:assetIndex])
|
||||
return asset.Item(table[:assetIndex])
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleDataResponse classifies the WS response and sends to appropriate handler
|
||||
func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) {
|
||||
switch o.GetWsChannelWithoutOrderType(response.Table) {
|
||||
case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s,
|
||||
okGroupWsCandle900s, okGroupWsCandle1800s, okGroupWsCandle3600s,
|
||||
okGroupWsCandle7200s, okGroupWsCandle14400s, okGroupWsCandle21600s,
|
||||
okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s:
|
||||
o.wsProcessCandles(response)
|
||||
case okGroupWsDepth, okGroupWsDepth5:
|
||||
// Locking, orderbooks cannot be processed out of order
|
||||
orderbookMutex.Lock()
|
||||
err := o.WsProcessOrderBook(response)
|
||||
if err != nil {
|
||||
for i := range response.Data {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterDash)
|
||||
default:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
channelToResubscribe := wshandler.WebsocketChannelSubscription{
|
||||
Channel: response.Table,
|
||||
Currency: c,
|
||||
}
|
||||
o.Websocket.ResubscribeToChannel(channelToResubscribe)
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
default:
|
||||
resp, err := o.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
o.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
o.Websocket.TrafficAlert <- struct{}{}
|
||||
err = o.WsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
orderbookMutex.Unlock()
|
||||
case okGroupWsTicker:
|
||||
o.wsProcessTickers(response)
|
||||
case okGroupWsTrade:
|
||||
o.wsProcessTrades(response)
|
||||
default:
|
||||
logDataResponse(response, o.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// logDataResponse will log the details of any websocket data event
|
||||
// where there is no websocket datahandler for it
|
||||
func logDataResponse(response *WebsocketDataResponse, exchangeName string) {
|
||||
for i := range response.Data {
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s Unhandled channel: '%v'. Instrument '%v' Timestamp '%v'",
|
||||
exchangeName,
|
||||
response.Table,
|
||||
response.Data[i].InstrumentID,
|
||||
response.Data[i].Timestamp)
|
||||
// WsHandleData will read websocket raw data and pass to appropriate handler
|
||||
func (o *OKGroup) WsHandleData(respRaw []byte) error {
|
||||
var dataResponse WebsocketDataResponse
|
||||
err := json.Unmarshal(respRaw, &dataResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dataResponse.Data) > 0 {
|
||||
switch o.GetWsChannelWithoutOrderType(dataResponse.Table) {
|
||||
case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s,
|
||||
okGroupWsCandle900s, okGroupWsCandle1800s, okGroupWsCandle3600s,
|
||||
okGroupWsCandle7200s, okGroupWsCandle14400s, okGroupWsCandle21600s,
|
||||
okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s:
|
||||
return o.wsProcessCandles(respRaw)
|
||||
case okGroupWsDepth, okGroupWsDepth5:
|
||||
return o.WsProcessOrderBook(respRaw)
|
||||
case okGroupWsTicker:
|
||||
return o.wsProcessTickers(respRaw)
|
||||
case okGroupWsTrade:
|
||||
return o.wsProcessTrades(respRaw)
|
||||
case okGroupWsOrder:
|
||||
return o.wsProcessOrder(respRaw)
|
||||
}
|
||||
o.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: o.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
|
||||
var errorResponse WebsocketErrorResponse
|
||||
err = json.Unmarshal(respRaw, &errorResponse)
|
||||
if err == nil && errorResponse.ErrorCode > 0 {
|
||||
return fmt.Errorf("%v error - %v message: %s ",
|
||||
o.Name,
|
||||
errorResponse.ErrorCode,
|
||||
errorResponse.Message)
|
||||
}
|
||||
var eventResponse WebsocketEventResponse
|
||||
err = json.Unmarshal(respRaw, &eventResponse)
|
||||
if err == nil && eventResponse.Event != "" {
|
||||
if eventResponse.Event == "login" {
|
||||
o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success)
|
||||
}
|
||||
if o.Verbose {
|
||||
log.Debug(log.ExchangeSys,
|
||||
o.Name+" - "+eventResponse.Event+" on channel: "+eventResponse.Channel)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringToOrderStatus converts order status IDs to internal types
|
||||
func StringToOrderStatus(num int64) (order.Status, error) {
|
||||
switch num {
|
||||
case -2:
|
||||
return order.Rejected, nil
|
||||
case -1:
|
||||
return order.Cancelled, nil
|
||||
case 0:
|
||||
return order.Active, nil
|
||||
case 1:
|
||||
return order.PartiallyFilled, nil
|
||||
case 2:
|
||||
return order.Filled, nil
|
||||
case 3:
|
||||
return order.New, nil
|
||||
case 4:
|
||||
return order.PendingCancel, nil
|
||||
default:
|
||||
return order.UnknownStatus, fmt.Errorf("%v not recognised as order status", num)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OKGroup) wsProcessOrder(respRaw []byte) error {
|
||||
var resp WebsocketSpotOrderResponse
|
||||
err := json.Unmarshal(respRaw, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range resp.Data {
|
||||
var oType order.Type
|
||||
var oSide order.Side
|
||||
var oStatus order.Status
|
||||
oType, err = order.StringToOrderType(resp.Data[i].Type)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: o.Name,
|
||||
OrderID: resp.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oSide, err = order.StringToOrderSide(resp.Data[i].Side)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: o.Name,
|
||||
OrderID: resp.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
oStatus, err = StringToOrderStatus(resp.Data[i].State)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: o.Name,
|
||||
OrderID: resp.Data[i].OrderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
o.Websocket.DataHandler <- &order.Detail{
|
||||
ImmediateOrCancel: resp.Data[i].OrderType == 3,
|
||||
FillOrKill: resp.Data[i].OrderType == 2,
|
||||
PostOnly: resp.Data[i].OrderType == 1,
|
||||
Price: resp.Data[i].Price,
|
||||
Amount: resp.Data[i].Size,
|
||||
ExecutedAmount: resp.Data[i].LastFillQty,
|
||||
RemainingAmount: resp.Data[i].Size - resp.Data[i].LastFillQty,
|
||||
Exchange: o.Name,
|
||||
ID: resp.Data[i].OrderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: o.GetAssetTypeFromTableName(resp.Table),
|
||||
Date: resp.Data[i].CreatedAt,
|
||||
Pair: currency.NewPairFromString(resp.Data[i].InstrumentID),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
|
||||
func (o *OKGroup) wsProcessTickers(respRaw []byte) error {
|
||||
var response WebsocketTickerData
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range response.Data {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
@@ -410,7 +404,6 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: o.Name,
|
||||
Open: response.Data[i].Open24h,
|
||||
@@ -422,15 +415,21 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
|
||||
Bid: response.Data[i].BestBid,
|
||||
Ask: response.Data[i].BestAsk,
|
||||
Last: response.Data[i].Last,
|
||||
LastUpdated: response.Data[i].Timestamp,
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
Pair: c,
|
||||
LastUpdated: response.Data[i].Timestamp,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsProcessTrades converts trade data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) {
|
||||
func (o *OKGroup) wsProcessTrades(respRaw []byte) error {
|
||||
var response WebsocketTradeResponse
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range response.Data {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
@@ -442,21 +441,33 @@ func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) {
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
tSide, err := order.StringToOrderSide(response.Data[i].Side)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: o.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
o.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Amount: response.Data[i].Size,
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
CurrencyPair: c,
|
||||
Exchange: o.Name,
|
||||
Price: response.Data[i].WebsocketTradeResponse.Price,
|
||||
Side: response.Data[i].Side,
|
||||
Price: response.Data[i].Price,
|
||||
Side: tSide,
|
||||
Timestamp: response.Data[i].Timestamp,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
|
||||
func (o *OKGroup) wsProcessCandles(respRaw []byte) error {
|
||||
var response WebsocketCandleResponse
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range response.Data {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
@@ -470,10 +481,9 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
|
||||
}
|
||||
|
||||
timeData, err := time.Parse(time.RFC3339Nano,
|
||||
response.Data[i].WebsocketCandleResponse.Candle[0])
|
||||
response.Data[i].Candle[0])
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%v Time data could not be parsed: %v",
|
||||
return fmt.Errorf("%v Time data could not be parsed: %v",
|
||||
o.Name,
|
||||
response.Data[i].Candle[0])
|
||||
}
|
||||
@@ -494,36 +504,39 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
|
||||
}
|
||||
klineData.OpenPrice, err = strconv.ParseFloat(response.Data[i].Candle[1], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
return err
|
||||
}
|
||||
klineData.HighPrice, err = strconv.ParseFloat(response.Data[i].Candle[2], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
return err
|
||||
}
|
||||
klineData.LowPrice, err = strconv.ParseFloat(response.Data[i].Candle[3], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
return err
|
||||
}
|
||||
klineData.ClosePrice, err = strconv.ParseFloat(response.Data[i].Candle[4], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
return err
|
||||
}
|
||||
klineData.Volume, err = strconv.ParseFloat(response.Data[i].Candle[5], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
return err
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- klineData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsProcessOrderBook Validates the checksum and updates internal orderbook values
|
||||
func (o *OKGroup) WsProcessOrderBook(response *WebsocketDataResponse) (err error) {
|
||||
func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error {
|
||||
var response WebsocketOrderBooksData
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderbookMutex.Lock()
|
||||
defer orderbookMutex.Unlock()
|
||||
for i := range response.Data {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
@@ -537,21 +550,44 @@ func (o *OKGroup) WsProcessOrderBook(response *WebsocketDataResponse) (err error
|
||||
}
|
||||
|
||||
if response.Action == okGroupWsOrderbookPartial {
|
||||
err = o.WsProcessPartialOrderBook(&response.Data[i], c, a)
|
||||
err := o.WsProcessPartialOrderBook(&response.Data[i], c, a)
|
||||
if err != nil {
|
||||
return
|
||||
o.wsResubscribeToOrderbook(&response)
|
||||
return err
|
||||
}
|
||||
} else if response.Action == okGroupWsOrderbookUpdate {
|
||||
if len(response.Data[i].Asks) == 0 && len(response.Data[i].Bids) == 0 {
|
||||
continue
|
||||
return nil
|
||||
}
|
||||
err = o.WsProcessUpdateOrderbook(&response.Data[i], c, a)
|
||||
err := o.WsProcessUpdateOrderbook(&response.Data[i], c, a)
|
||||
if err != nil {
|
||||
return
|
||||
o.wsResubscribeToOrderbook(&response)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OKGroup) wsResubscribeToOrderbook(response *WebsocketOrderBooksData) {
|
||||
for i := range response.Data {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterDash)
|
||||
default:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
channelToResubscribe := wshandler.WebsocketChannelSubscription{
|
||||
Channel: response.Table,
|
||||
Currency: c,
|
||||
}
|
||||
o.Websocket.ResubscribeToChannel(channelToResubscribe)
|
||||
}
|
||||
}
|
||||
|
||||
// AppendWsOrderbookItems adds websocket orderbook data bid/asks into an orderbook item array
|
||||
@@ -573,7 +609,7 @@ func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.I
|
||||
|
||||
// WsProcessPartialOrderBook takes websocket orderbook data and creates an orderbook
|
||||
// Calculates checksum to ensure it is valid
|
||||
func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, a asset.Item) error {
|
||||
func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error {
|
||||
signedChecksum := o.CalculatePartialOrderbookChecksum(wsEventData)
|
||||
if signedChecksum != wsEventData.Checksum {
|
||||
return fmt.Errorf("%s channel: %s. Orderbook partial for %v checksum invalid",
|
||||
@@ -622,7 +658,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i
|
||||
|
||||
// WsProcessUpdateOrderbook updates an existing orderbook using websocket data
|
||||
// After merging WS data, it will sort, validate and finally update the existing orderbook
|
||||
func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, a asset.Item) error {
|
||||
func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error {
|
||||
update := wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asset: a,
|
||||
Pair: instrument,
|
||||
@@ -669,7 +705,7 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in
|
||||
// quantity with a semicolon (:) deliminating them. This will also work when
|
||||
// there are less than 25 entries (for whatever reason)
|
||||
// eg Bid:Ask:Bid:Ask:Ask:Ask
|
||||
func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketDataWrapper) int32 {
|
||||
func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketOrderBook) int32 {
|
||||
var checksum strings.Builder
|
||||
for i := 0; i < allowableIterations; i++ {
|
||||
if len(orderbookData.Bids)-1 >= i {
|
||||
@@ -840,3 +876,41 @@ func (o *OKGroup) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubsc
|
||||
}
|
||||
return o.WebsocketConn.SendJSONMessage(request)
|
||||
}
|
||||
|
||||
// GetWsChannelWithoutOrderType takes WebsocketDataResponse.Table and returns
|
||||
// The base channel name eg receive "spot/depth5:BTC-USDT" return "depth5"
|
||||
func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string {
|
||||
index := strings.Index(table, "/")
|
||||
if index == -1 {
|
||||
return table
|
||||
}
|
||||
channel := table[index+1:]
|
||||
index = strings.Index(channel, ":")
|
||||
// Some events do not contain a currency
|
||||
if index == -1 {
|
||||
return channel
|
||||
}
|
||||
|
||||
return channel[:index]
|
||||
}
|
||||
|
||||
// GetAssetTypeFromTableName gets the asset type from the table name
|
||||
// eg "spot/ticker:BTCUSD" results in "SPOT"
|
||||
func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item {
|
||||
assetIndex := strings.Index(table, "/")
|
||||
switch table[:assetIndex] {
|
||||
case asset.Futures.String():
|
||||
return asset.Futures
|
||||
case asset.Spot.String():
|
||||
return asset.Spot
|
||||
case "swap":
|
||||
return asset.PerpetualSwap
|
||||
case asset.Index.String():
|
||||
return asset.Index
|
||||
default:
|
||||
log.Warnf(log.ExchangeSys, "%s unhandled asset type %s",
|
||||
o.Name,
|
||||
table[:assetIndex])
|
||||
return asset.Item(table[:assetIndex])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,11 +276,11 @@ func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err e
|
||||
request := PlaceOrderRequest{
|
||||
ClientOID: s.ClientID,
|
||||
InstrumentID: o.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
Side: s.OrderSide.Lower(),
|
||||
Type: s.OrderType.Lower(),
|
||||
Side: s.Side.Lower(),
|
||||
Type: s.Type.Lower(),
|
||||
Size: strconv.FormatFloat(s.Amount, 'f', -1, 64),
|
||||
}
|
||||
if s.OrderType == order.Limit {
|
||||
if s.Type == order.Limit {
|
||||
request.Price = strconv.FormatFloat(s.Price, 'f', -1, 64)
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err e
|
||||
|
||||
resp.IsOrderPlaced = orderResponse.Result
|
||||
resp.OrderID = orderResponse.OrderID
|
||||
if s.OrderType == order.Market {
|
||||
if s.Type == order.Market {
|
||||
resp.FullyMatched = true
|
||||
}
|
||||
return
|
||||
@@ -305,12 +305,12 @@ func (o *OKGroup) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (o *OKGroup) CancelOrder(orderCancellation *order.Cancel) (err error) {
|
||||
orderID, err := strconv.ParseInt(orderCancellation.OrderID, 10, 64)
|
||||
orderID, err := strconv.ParseInt(orderCancellation.ID, 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
orderCancellationResponse, err := o.CancelSpotOrder(CancelSpotOrderRequest{
|
||||
InstrumentID: o.FormatExchangeCurrency(orderCancellation.CurrencyPair,
|
||||
InstrumentID: o.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
asset.Spot).String(),
|
||||
OrderID: orderID,
|
||||
})
|
||||
@@ -324,7 +324,7 @@ func (o *OKGroup) CancelOrder(orderCancellation *order.Cancel) (err error) {
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
func (o *OKGroup) CancelAllOrders(orderCancellation *order.Cancel) (resp order.CancelAllResponse, err error) {
|
||||
orderIDs := strings.Split(orderCancellation.OrderID, ",")
|
||||
orderIDs := strings.Split(orderCancellation.ID, ",")
|
||||
resp.Status = make(map[string]string)
|
||||
var orderIDNumbers []int64
|
||||
for i := range orderIDs {
|
||||
@@ -337,7 +337,7 @@ func (o *OKGroup) CancelAllOrders(orderCancellation *order.Cancel) (resp order.C
|
||||
}
|
||||
|
||||
cancelOrdersResponse, err := o.CancelMultipleSpotOrders(CancelMultipleSpotOrdersRequest{
|
||||
InstrumentID: o.FormatExchangeCurrency(orderCancellation.CurrencyPair,
|
||||
InstrumentID: o.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
asset.Spot).String(),
|
||||
OrderIDs: orderIDNumbers,
|
||||
})
|
||||
@@ -362,13 +362,13 @@ func (o *OKGroup) GetOrderInfo(orderID string) (resp order.Detail, err error) {
|
||||
}
|
||||
resp = order.Detail{
|
||||
Amount: mOrder.Size,
|
||||
CurrencyPair: currency.NewPairDelimiter(mOrder.InstrumentID,
|
||||
Pair: currency.NewPairDelimiter(mOrder.InstrumentID,
|
||||
o.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
Exchange: o.Name,
|
||||
OrderDate: mOrder.Timestamp,
|
||||
Date: mOrder.Timestamp,
|
||||
ExecutedAmount: mOrder.FilledSize,
|
||||
Status: order.Status(mOrder.Status),
|
||||
OrderSide: order.Side(mOrder.Side),
|
||||
Side: order.Side(mOrder.Side),
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -422,9 +422,9 @@ func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (o *OKGroup) GetActiveOrders(req *order.GetOrdersRequest) (resp []order.Detail, err error) {
|
||||
for x := range req.Currencies {
|
||||
for x := range req.Pairs {
|
||||
spotOpenOrders, err := o.GetSpotOpenOrders(GetSpotOpenOrdersRequest{
|
||||
InstrumentID: o.FormatExchangeCurrency(req.Currencies[x],
|
||||
InstrumentID: o.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -435,12 +435,12 @@ func (o *OKGroup) GetActiveOrders(req *order.GetOrdersRequest) (resp []order.Det
|
||||
ID: spotOpenOrders[i].OrderID,
|
||||
Price: spotOpenOrders[i].Price,
|
||||
Amount: spotOpenOrders[i].Size,
|
||||
CurrencyPair: req.Currencies[x],
|
||||
Pair: req.Pairs[x],
|
||||
Exchange: o.Name,
|
||||
OrderSide: order.Side(spotOpenOrders[i].Side),
|
||||
OrderType: order.Type(spotOpenOrders[i].Type),
|
||||
Side: order.Side(spotOpenOrders[i].Side),
|
||||
Type: order.Type(spotOpenOrders[i].Type),
|
||||
ExecutedAmount: spotOpenOrders[i].FilledSize,
|
||||
OrderDate: spotOpenOrders[i].Timestamp,
|
||||
Date: spotOpenOrders[i].Timestamp,
|
||||
Status: order.Status(spotOpenOrders[i].Status),
|
||||
})
|
||||
}
|
||||
@@ -452,10 +452,10 @@ func (o *OKGroup) GetActiveOrders(req *order.GetOrdersRequest) (resp []order.Det
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (o *OKGroup) GetOrderHistory(req *order.GetOrdersRequest) (resp []order.Detail, err error) {
|
||||
for x := range req.Currencies {
|
||||
for x := range req.Pairs {
|
||||
spotOpenOrders, err := o.GetSpotOrders(GetSpotOrdersRequest{
|
||||
Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"),
|
||||
InstrumentID: o.FormatExchangeCurrency(req.Currencies[x],
|
||||
InstrumentID: o.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -466,12 +466,12 @@ func (o *OKGroup) GetOrderHistory(req *order.GetOrdersRequest) (resp []order.Det
|
||||
ID: spotOpenOrders[i].OrderID,
|
||||
Price: spotOpenOrders[i].Price,
|
||||
Amount: spotOpenOrders[i].Size,
|
||||
CurrencyPair: req.Currencies[x],
|
||||
Pair: req.Pairs[x],
|
||||
Exchange: o.Name,
|
||||
OrderSide: order.Side(spotOpenOrders[i].Side),
|
||||
OrderType: order.Type(spotOpenOrders[i].Type),
|
||||
Side: order.Side(spotOpenOrders[i].Side),
|
||||
Type: order.Type(spotOpenOrders[i].Type),
|
||||
ExecutedAmount: spotOpenOrders[i].FilledSize,
|
||||
OrderDate: spotOpenOrders[i].Timestamp,
|
||||
Date: spotOpenOrders[i].Timestamp,
|
||||
Status: order.Status(spotOpenOrders[i].Status),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -71,11 +71,11 @@ func TestValidate(t *testing.T) {
|
||||
|
||||
for x := range tester {
|
||||
s := Submit{
|
||||
Pair: tester[x].Pair,
|
||||
OrderSide: tester[x].Side,
|
||||
OrderType: tester[x].Type,
|
||||
Amount: tester[x].Amount,
|
||||
Price: tester[x].Price,
|
||||
Pair: tester[x].Pair,
|
||||
Side: tester[x].Side,
|
||||
Type: tester[x].Type,
|
||||
Amount: tester[x].Amount,
|
||||
Price: tester[x].Price,
|
||||
}
|
||||
if err := s.Validate(); err != tester[x].ExpectedErr {
|
||||
t.Errorf("Unexpected result. Got: %s, want: %s", err, tester[x].ExpectedErr)
|
||||
@@ -115,10 +115,10 @@ func TestFilterOrdersByType(t *testing.T) {
|
||||
|
||||
var orders = []Detail{
|
||||
{
|
||||
OrderType: ImmediateOrCancel,
|
||||
Type: ImmediateOrCancel,
|
||||
},
|
||||
{
|
||||
OrderType: Limit,
|
||||
Type: Limit,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -143,10 +143,10 @@ func TestFilterOrdersBySide(t *testing.T) {
|
||||
|
||||
var orders = []Detail{
|
||||
{
|
||||
OrderSide: Buy,
|
||||
Side: Buy,
|
||||
},
|
||||
{
|
||||
OrderSide: Sell,
|
||||
Side: Sell,
|
||||
},
|
||||
{},
|
||||
}
|
||||
@@ -172,13 +172,13 @@ func TestFilterOrdersByTickRange(t *testing.T) {
|
||||
|
||||
var orders = []Detail{
|
||||
{
|
||||
OrderDate: time.Unix(100, 0),
|
||||
Date: time.Unix(100, 0),
|
||||
},
|
||||
{
|
||||
OrderDate: time.Unix(110, 0),
|
||||
Date: time.Unix(110, 0),
|
||||
},
|
||||
{
|
||||
OrderDate: time.Unix(111, 0),
|
||||
Date: time.Unix(111, 0),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -208,13 +208,13 @@ func TestFilterOrdersByCurrencies(t *testing.T) {
|
||||
|
||||
var orders = []Detail{
|
||||
{
|
||||
CurrencyPair: currency.NewPair(currency.BTC, currency.USD),
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
},
|
||||
{
|
||||
CurrencyPair: currency.NewPair(currency.LTC, currency.EUR),
|
||||
Pair: currency.NewPair(currency.LTC, currency.EUR),
|
||||
},
|
||||
{
|
||||
CurrencyPair: currency.NewPair(currency.DOGE, currency.RUB),
|
||||
Pair: currency.NewPair(currency.DOGE, currency.RUB),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -275,26 +275,26 @@ func TestSortOrdersByDate(t *testing.T) {
|
||||
|
||||
orders := []Detail{
|
||||
{
|
||||
OrderDate: time.Unix(0, 0),
|
||||
Date: time.Unix(0, 0),
|
||||
}, {
|
||||
OrderDate: time.Unix(1, 0),
|
||||
Date: time.Unix(1, 0),
|
||||
}, {
|
||||
OrderDate: time.Unix(2, 0),
|
||||
Date: time.Unix(2, 0),
|
||||
},
|
||||
}
|
||||
|
||||
SortOrdersByDate(&orders, false)
|
||||
if orders[0].OrderDate.Unix() != time.Unix(0, 0).Unix() {
|
||||
if orders[0].Date.Unix() != time.Unix(0, 0).Unix() {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
time.Unix(0, 0).Unix(),
|
||||
orders[0].OrderDate.Unix())
|
||||
orders[0].Date.Unix())
|
||||
}
|
||||
|
||||
SortOrdersByDate(&orders, true)
|
||||
if orders[0].OrderDate.Unix() != time.Unix(2, 0).Unix() {
|
||||
if orders[0].Date.Unix() != time.Unix(2, 0).Unix() {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
time.Unix(2, 0).Unix(),
|
||||
orders[0].OrderDate.Unix())
|
||||
orders[0].Date.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,40 +303,40 @@ func TestSortOrdersByCurrency(t *testing.T) {
|
||||
|
||||
orders := []Detail{
|
||||
{
|
||||
CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
currency.USD.String(),
|
||||
"-"),
|
||||
}, {
|
||||
CurrencyPair: currency.NewPairWithDelimiter(currency.DOGE.String(),
|
||||
Pair: currency.NewPairWithDelimiter(currency.DOGE.String(),
|
||||
currency.USD.String(),
|
||||
"-"),
|
||||
}, {
|
||||
CurrencyPair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
currency.RUB.String(),
|
||||
"-"),
|
||||
}, {
|
||||
CurrencyPair: currency.NewPairWithDelimiter(currency.LTC.String(),
|
||||
Pair: currency.NewPairWithDelimiter(currency.LTC.String(),
|
||||
currency.EUR.String(),
|
||||
"-"),
|
||||
}, {
|
||||
CurrencyPair: currency.NewPairWithDelimiter(currency.LTC.String(),
|
||||
Pair: currency.NewPairWithDelimiter(currency.LTC.String(),
|
||||
currency.AUD.String(),
|
||||
"-"),
|
||||
},
|
||||
}
|
||||
|
||||
SortOrdersByCurrency(&orders, false)
|
||||
if orders[0].CurrencyPair.String() != currency.BTC.String()+"-"+currency.RUB.String() {
|
||||
if orders[0].Pair.String() != currency.BTC.String()+"-"+currency.RUB.String() {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
currency.BTC.String()+"-"+currency.RUB.String(),
|
||||
orders[0].CurrencyPair.String())
|
||||
orders[0].Pair.String())
|
||||
}
|
||||
|
||||
SortOrdersByCurrency(&orders, true)
|
||||
if orders[0].CurrencyPair.String() != currency.LTC.String()+"-"+currency.EUR.String() {
|
||||
if orders[0].Pair.String() != currency.LTC.String()+"-"+currency.EUR.String() {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
currency.LTC.String()+"-"+currency.EUR.String(),
|
||||
orders[0].CurrencyPair.String())
|
||||
orders[0].Pair.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,28 +345,28 @@ func TestSortOrdersByOrderSide(t *testing.T) {
|
||||
|
||||
orders := []Detail{
|
||||
{
|
||||
OrderSide: Buy,
|
||||
Side: Buy,
|
||||
}, {
|
||||
OrderSide: Sell,
|
||||
Side: Sell,
|
||||
}, {
|
||||
OrderSide: Sell,
|
||||
Side: Sell,
|
||||
}, {
|
||||
OrderSide: Buy,
|
||||
Side: Buy,
|
||||
},
|
||||
}
|
||||
|
||||
SortOrdersBySide(&orders, false)
|
||||
if !strings.EqualFold(orders[0].OrderSide.String(), Buy.String()) {
|
||||
if !strings.EqualFold(orders[0].Side.String(), Buy.String()) {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
Buy,
|
||||
orders[0].OrderSide)
|
||||
orders[0].Side)
|
||||
}
|
||||
|
||||
SortOrdersBySide(&orders, true)
|
||||
if !strings.EqualFold(orders[0].OrderSide.String(), Sell.String()) {
|
||||
if !strings.EqualFold(orders[0].Side.String(), Sell.String()) {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
Sell,
|
||||
orders[0].OrderSide)
|
||||
orders[0].Side)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,28 +375,28 @@ func TestSortOrdersByOrderType(t *testing.T) {
|
||||
|
||||
orders := []Detail{
|
||||
{
|
||||
OrderType: Market,
|
||||
Type: Market,
|
||||
}, {
|
||||
OrderType: Limit,
|
||||
Type: Limit,
|
||||
}, {
|
||||
OrderType: ImmediateOrCancel,
|
||||
Type: ImmediateOrCancel,
|
||||
}, {
|
||||
OrderType: TrailingStop,
|
||||
Type: TrailingStop,
|
||||
},
|
||||
}
|
||||
|
||||
SortOrdersByType(&orders, false)
|
||||
if !strings.EqualFold(orders[0].OrderType.String(), ImmediateOrCancel.String()) {
|
||||
if !strings.EqualFold(orders[0].Type.String(), ImmediateOrCancel.String()) {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
ImmediateOrCancel,
|
||||
orders[0].OrderType)
|
||||
orders[0].Type)
|
||||
}
|
||||
|
||||
SortOrdersByType(&orders, true)
|
||||
if !strings.EqualFold(orders[0].OrderType.String(), TrailingStop.String()) {
|
||||
if !strings.EqualFold(orders[0].Type.String(), TrailingStop.String()) {
|
||||
t.Errorf("Expected: '%v', received: '%v'",
|
||||
TrailingStop,
|
||||
orders[0].OrderType)
|
||||
orders[0].Type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ var stringsToOrderSide = []struct {
|
||||
{"any", AnySide, nil},
|
||||
{"ANY", AnySide, nil},
|
||||
{"aNy", AnySide, nil},
|
||||
{"woahMan", Buy, errors.New("woahMan not recognised as side type")},
|
||||
{"woahMan", Buy, errors.New("woahMan not recognised as order side")},
|
||||
}
|
||||
|
||||
func TestStringToOrderSide(t *testing.T) {
|
||||
@@ -453,16 +453,18 @@ var stringsToOrderType = []struct {
|
||||
{"immediate_or_cancel", ImmediateOrCancel, nil},
|
||||
{"IMMEDIATE_OR_CANCEL", ImmediateOrCancel, nil},
|
||||
{"iMmEdIaTe_Or_CaNcEl", ImmediateOrCancel, nil},
|
||||
{"iMmEdIaTe Or CaNcEl", ImmediateOrCancel, nil},
|
||||
{"stop", Stop, nil},
|
||||
{"STOP", Stop, nil},
|
||||
{"sToP", Stop, nil},
|
||||
{"trailingstop", TrailingStop, nil},
|
||||
{"TRAILINGSTOP", TrailingStop, nil},
|
||||
{"tRaIlInGsToP", TrailingStop, nil},
|
||||
{"trailing_stop", TrailingStop, nil},
|
||||
{"TRAILING_STOP", TrailingStop, nil},
|
||||
{"tRaIlInG_sToP", TrailingStop, nil},
|
||||
{"tRaIlInG sToP", TrailingStop, nil},
|
||||
{"any", AnyType, nil},
|
||||
{"ANY", AnyType, nil},
|
||||
{"aNy", AnyType, nil},
|
||||
{"woahMan", Unknown, errors.New("woahMan not recognised as order type")},
|
||||
{"woahMan", UnknownType, errors.New("woahMan not recognised as order type")},
|
||||
}
|
||||
|
||||
func TestStringToOrderType(t *testing.T) {
|
||||
@@ -516,7 +518,13 @@ var stringsToOrderStatus = []struct {
|
||||
{"hidden", Hidden, nil},
|
||||
{"HIDDEN", Hidden, nil},
|
||||
{"hIdDeN", Hidden, nil},
|
||||
{"woahMan", UnknownStatus, errors.New("woahMan not recognised as order STATUS")},
|
||||
{"market_unavailable", MarketUnavailable, nil},
|
||||
{"MARKET_UNAVAILABLE", MarketUnavailable, nil},
|
||||
{"mArKeT_uNaVaIlAbLe", MarketUnavailable, nil},
|
||||
{"insufficient_balance", InsufficientBalance, nil},
|
||||
{"INSUFFICIENT_BALANCE", InsufficientBalance, nil},
|
||||
{"iNsUfFiCiEnT_bAlAnCe", InsufficientBalance, nil},
|
||||
{"woahMan", UnknownStatus, errors.New("woahMan not recognised as order status")},
|
||||
}
|
||||
|
||||
func TestStringToOrderStatus(t *testing.T) {
|
||||
@@ -534,3 +542,375 @@ func TestStringToOrderStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOrderFromModify(t *testing.T) {
|
||||
var leet = "1337"
|
||||
od := Detail{
|
||||
ImmediateOrCancel: false,
|
||||
HiddenOrder: false,
|
||||
FillOrKill: false,
|
||||
PostOnly: false,
|
||||
Leverage: "",
|
||||
Price: 0,
|
||||
Amount: 0,
|
||||
LimitPriceUpper: 0,
|
||||
LimitPriceLower: 0,
|
||||
TriggerPrice: 0,
|
||||
TargetAmount: 0,
|
||||
ExecutedAmount: 0,
|
||||
RemainingAmount: 0,
|
||||
Fee: 0,
|
||||
Exchange: "",
|
||||
ID: "1",
|
||||
AccountID: "",
|
||||
ClientID: "",
|
||||
WalletAddress: "",
|
||||
Type: "",
|
||||
Side: "",
|
||||
Status: "",
|
||||
AssetType: "",
|
||||
Date: time.Time{},
|
||||
LastUpdated: time.Time{},
|
||||
Pair: currency.Pair{},
|
||||
Trades: nil,
|
||||
}
|
||||
updated := time.Now()
|
||||
om := Modify{
|
||||
ImmediateOrCancel: true,
|
||||
HiddenOrder: true,
|
||||
FillOrKill: true,
|
||||
PostOnly: true,
|
||||
Leverage: "1",
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
LimitPriceUpper: 1,
|
||||
LimitPriceLower: 1,
|
||||
TriggerPrice: 1,
|
||||
TargetAmount: 1,
|
||||
ExecutedAmount: 1,
|
||||
RemainingAmount: 1,
|
||||
Fee: 1,
|
||||
Exchange: "1",
|
||||
InternalOrderID: "1",
|
||||
ID: "1",
|
||||
AccountID: "1",
|
||||
ClientID: "1",
|
||||
WalletAddress: "1",
|
||||
Type: "1",
|
||||
Side: "1",
|
||||
Status: "1",
|
||||
AssetType: "1",
|
||||
LastUpdated: updated,
|
||||
Pair: currency.NewPairFromString("BTCUSD"),
|
||||
Trades: []TradeHistory{},
|
||||
}
|
||||
|
||||
od.UpdateOrderFromModify(&om)
|
||||
if od.InternalOrderID == "1" {
|
||||
t.Error("Should not be able to update the internal order ID")
|
||||
}
|
||||
if !od.ImmediateOrCancel {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if !od.HiddenOrder {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if !od.FillOrKill {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if !od.PostOnly {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Leverage != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Price != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Amount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.LimitPriceLower != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.LimitPriceUpper != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.TriggerPrice != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.TargetAmount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.ExecutedAmount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.RemainingAmount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Fee != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Exchange != "" {
|
||||
t.Error("Should not be able to update exchange via modify")
|
||||
}
|
||||
if od.ID != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.ClientID != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.WalletAddress != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Type != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Side != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Status != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.AssetType != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.LastUpdated != updated {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Pair.String() != "BTCUSD" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Trades != nil {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
|
||||
om.Trades = append(om.Trades, TradeHistory{TID: "1"}, TradeHistory{TID: "2"})
|
||||
od.UpdateOrderFromModify(&om)
|
||||
if len(od.Trades) != 2 {
|
||||
t.Error("Failed to add trades")
|
||||
}
|
||||
om.Trades[0].Exchange = leet
|
||||
om.Trades[0].Price = 1337
|
||||
om.Trades[0].Fee = 1337
|
||||
om.Trades[0].IsMaker = true
|
||||
om.Trades[0].Timestamp = updated
|
||||
om.Trades[0].Description = leet
|
||||
om.Trades[0].Side = UnknownSide
|
||||
om.Trades[0].Type = UnknownType
|
||||
om.Trades[0].Amount = 1337
|
||||
od.UpdateOrderFromModify(&om)
|
||||
if od.Trades[0].Exchange == leet {
|
||||
t.Error("Should not be able to update exchange from update")
|
||||
}
|
||||
if od.Trades[0].Price != 1337 {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Fee != 1337 {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if !od.Trades[0].IsMaker {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Timestamp != updated {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Description != leet {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Side != UnknownSide {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Type != UnknownType {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Amount != 1337 {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOrderFromDetail(t *testing.T) {
|
||||
var leet = "1337"
|
||||
od := Detail{
|
||||
ImmediateOrCancel: false,
|
||||
HiddenOrder: false,
|
||||
FillOrKill: false,
|
||||
PostOnly: false,
|
||||
Leverage: "",
|
||||
Price: 0,
|
||||
Amount: 0,
|
||||
LimitPriceUpper: 0,
|
||||
LimitPriceLower: 0,
|
||||
TriggerPrice: 0,
|
||||
TargetAmount: 0,
|
||||
ExecutedAmount: 0,
|
||||
RemainingAmount: 0,
|
||||
Fee: 0,
|
||||
Exchange: "",
|
||||
ID: "1",
|
||||
AccountID: "",
|
||||
ClientID: "",
|
||||
WalletAddress: "",
|
||||
Type: "",
|
||||
Side: "",
|
||||
Status: "",
|
||||
AssetType: "",
|
||||
Date: time.Time{},
|
||||
LastUpdated: time.Time{},
|
||||
Pair: currency.Pair{},
|
||||
Trades: nil,
|
||||
}
|
||||
updated := time.Now()
|
||||
om := Detail{
|
||||
ImmediateOrCancel: true,
|
||||
HiddenOrder: true,
|
||||
FillOrKill: true,
|
||||
PostOnly: true,
|
||||
Leverage: "1",
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
LimitPriceUpper: 1,
|
||||
LimitPriceLower: 1,
|
||||
TriggerPrice: 1,
|
||||
TargetAmount: 1,
|
||||
ExecutedAmount: 1,
|
||||
RemainingAmount: 1,
|
||||
Fee: 1,
|
||||
Exchange: "1",
|
||||
InternalOrderID: "1",
|
||||
ID: "1",
|
||||
AccountID: "1",
|
||||
ClientID: "1",
|
||||
WalletAddress: "1",
|
||||
Type: "1",
|
||||
Side: "1",
|
||||
Status: "1",
|
||||
AssetType: "1",
|
||||
LastUpdated: updated,
|
||||
Pair: currency.NewPairFromString("BTCUSD"),
|
||||
Trades: []TradeHistory{},
|
||||
}
|
||||
|
||||
od.UpdateOrderFromDetail(&om)
|
||||
if od.InternalOrderID == "1" {
|
||||
t.Error("Should not be able to update the internal order ID")
|
||||
}
|
||||
if !od.ImmediateOrCancel {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if !od.HiddenOrder {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if !od.FillOrKill {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if !od.PostOnly {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Leverage != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Price != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Amount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.LimitPriceLower != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.LimitPriceUpper != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.TriggerPrice != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.TargetAmount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.ExecutedAmount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.RemainingAmount != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Fee != 1 {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Exchange != "" {
|
||||
t.Error("Should not be able to update exchange via modify")
|
||||
}
|
||||
if od.ID != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.ClientID != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.WalletAddress != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Type != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Side != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Status != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.AssetType != "1" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.LastUpdated != updated {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Pair.String() != "BTCUSD" {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
if od.Trades != nil {
|
||||
t.Error("Failed to update")
|
||||
}
|
||||
|
||||
om.Trades = append(om.Trades, TradeHistory{TID: "1"}, TradeHistory{TID: "2"})
|
||||
od.UpdateOrderFromDetail(&om)
|
||||
if len(od.Trades) != 2 {
|
||||
t.Error("Failed to add trades")
|
||||
}
|
||||
om.Trades[0].Exchange = leet
|
||||
om.Trades[0].Price = 1337
|
||||
om.Trades[0].Fee = 1337
|
||||
om.Trades[0].IsMaker = true
|
||||
om.Trades[0].Timestamp = updated
|
||||
om.Trades[0].Description = leet
|
||||
om.Trades[0].Side = UnknownSide
|
||||
om.Trades[0].Type = UnknownType
|
||||
om.Trades[0].Amount = 1337
|
||||
od.UpdateOrderFromDetail(&om)
|
||||
if od.Trades[0].Exchange == leet {
|
||||
t.Error("Should not be able to update exchange from update")
|
||||
}
|
||||
if od.Trades[0].Price != 1337 {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Fee != 1337 {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if !od.Trades[0].IsMaker {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Timestamp != updated {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Description != leet {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Side != UnknownSide {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Type != UnknownType {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
if od.Trades[0].Amount != 1337 {
|
||||
t.Error("Failed to update trades")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,14 @@ package order
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
const (
|
||||
limitOrder = iota
|
||||
marketOrder
|
||||
)
|
||||
|
||||
// Orders variable holds an array of pointers to order structs
|
||||
var Orders []*Order
|
||||
|
||||
// Order struct holds order values
|
||||
type Order struct {
|
||||
OrderID int
|
||||
Exchange string
|
||||
Type int
|
||||
Amount float64
|
||||
Price float64
|
||||
}
|
||||
|
||||
// vars related to orders
|
||||
// var error definitions
|
||||
var (
|
||||
ErrSubmissionIsNil = errors.New("order submission is nil")
|
||||
ErrPairIsEmpty = errors.New("order pair is empty")
|
||||
@@ -35,16 +19,39 @@ var (
|
||||
ErrPriceMustBeSetIfLimitOrder = errors.New("order price must be set if limit order type is desired")
|
||||
)
|
||||
|
||||
// Submit contains the order submission data
|
||||
// Submit contains all properties of an order that may be required
|
||||
// for an order to be created on an exchange
|
||||
// Each exchange has their own requirements, so not all fields
|
||||
// are required to be populated
|
||||
type Submit struct {
|
||||
Pair currency.Pair
|
||||
OrderType Type
|
||||
OrderSide Side
|
||||
TriggerPrice float64
|
||||
TargetAmount float64
|
||||
Price float64
|
||||
Amount float64
|
||||
ClientID string
|
||||
ImmediateOrCancel bool
|
||||
HiddenOrder bool
|
||||
FillOrKill bool
|
||||
PostOnly bool
|
||||
Leverage string
|
||||
Price float64
|
||||
Amount float64
|
||||
LimitPriceUpper float64
|
||||
LimitPriceLower float64
|
||||
TriggerPrice float64
|
||||
TargetAmount float64
|
||||
ExecutedAmount float64
|
||||
RemainingAmount float64
|
||||
Fee float64
|
||||
Exchange string
|
||||
InternalOrderID string
|
||||
ID string
|
||||
AccountID string
|
||||
ClientID string
|
||||
WalletAddress string
|
||||
Type Type
|
||||
Side Side
|
||||
Status Status
|
||||
AssetType asset.Item
|
||||
Date time.Time
|
||||
LastUpdated time.Time
|
||||
Pair currency.Pair
|
||||
Trades []TradeHistory
|
||||
}
|
||||
|
||||
// SubmitResponse is what is returned after submitting an order to an exchange
|
||||
@@ -54,20 +61,39 @@ type SubmitResponse struct {
|
||||
OrderID string
|
||||
}
|
||||
|
||||
// Modify is an order modifyer
|
||||
// Modify contains all properties of an order
|
||||
// that may be updated after it has been created
|
||||
// Each exchange has their own requirements, so not all fields
|
||||
// are required to be populated
|
||||
type Modify struct {
|
||||
OrderID string
|
||||
Type
|
||||
Side
|
||||
Price float64
|
||||
Amount float64
|
||||
LimitPriceUpper float64
|
||||
LimitPriceLower float64
|
||||
CurrencyPair currency.Pair
|
||||
ImmediateOrCancel bool
|
||||
HiddenOrder bool
|
||||
FillOrKill bool
|
||||
PostOnly bool
|
||||
Leverage string
|
||||
Price float64
|
||||
Amount float64
|
||||
LimitPriceUpper float64
|
||||
LimitPriceLower float64
|
||||
TriggerPrice float64
|
||||
TargetAmount float64
|
||||
ExecutedAmount float64
|
||||
RemainingAmount float64
|
||||
Fee float64
|
||||
Exchange string
|
||||
InternalOrderID string
|
||||
ID string
|
||||
AccountID string
|
||||
ClientID string
|
||||
WalletAddress string
|
||||
Type Type
|
||||
Side Side
|
||||
Status Status
|
||||
AssetType asset.Item
|
||||
Date time.Time
|
||||
LastUpdated time.Time
|
||||
Pair currency.Pair
|
||||
Trades []TradeHistory
|
||||
}
|
||||
|
||||
// ModifyResponse is an order modifying return type
|
||||
@@ -75,12 +101,113 @@ type ModifyResponse struct {
|
||||
OrderID string
|
||||
}
|
||||
|
||||
// CancelAllResponse returns the status from attempting to cancel all orders on
|
||||
// an exchagne
|
||||
// Detail contains all properties of an order
|
||||
// Each exchange has their own requirements, so not all fields
|
||||
// are required to be populated
|
||||
type Detail struct {
|
||||
ImmediateOrCancel bool
|
||||
HiddenOrder bool
|
||||
FillOrKill bool
|
||||
PostOnly bool
|
||||
Leverage string
|
||||
Price float64
|
||||
Amount float64
|
||||
LimitPriceUpper float64
|
||||
LimitPriceLower float64
|
||||
TriggerPrice float64
|
||||
TargetAmount float64
|
||||
ExecutedAmount float64
|
||||
RemainingAmount float64
|
||||
Fee float64
|
||||
Exchange string
|
||||
InternalOrderID string
|
||||
ID string
|
||||
AccountID string
|
||||
ClientID string
|
||||
WalletAddress string
|
||||
Type Type
|
||||
Side Side
|
||||
Status Status
|
||||
AssetType asset.Item
|
||||
Date time.Time
|
||||
LastUpdated time.Time
|
||||
Pair currency.Pair
|
||||
Trades []TradeHistory
|
||||
}
|
||||
|
||||
// Cancel contains all properties that may be required
|
||||
// to cancel an order on an exchange
|
||||
// Each exchange has their own requirements, so not all fields
|
||||
// are required to be populated
|
||||
type Cancel struct {
|
||||
Price float64
|
||||
Amount float64
|
||||
Exchange string
|
||||
ID string
|
||||
AccountID string
|
||||
ClientID string
|
||||
WalletAddress string
|
||||
Type Type
|
||||
Side Side
|
||||
Status Status
|
||||
AssetType asset.Item
|
||||
Date time.Time
|
||||
Pair currency.Pair
|
||||
Trades []TradeHistory
|
||||
}
|
||||
|
||||
// CancelAllResponse returns the status from attempting to
|
||||
// cancel all orders on an exchange
|
||||
type CancelAllResponse struct {
|
||||
Status map[string]string
|
||||
}
|
||||
|
||||
// TradeHistory holds exchange history data
|
||||
type TradeHistory struct {
|
||||
Price float64
|
||||
Amount float64
|
||||
Fee float64
|
||||
Exchange string
|
||||
TID string
|
||||
Description string
|
||||
Type Type
|
||||
Side Side
|
||||
Timestamp time.Time
|
||||
IsMaker bool
|
||||
}
|
||||
|
||||
// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions
|
||||
type GetOrdersRequest struct {
|
||||
Type Type
|
||||
Side Side
|
||||
StartTicks time.Time
|
||||
EndTicks time.Time
|
||||
// Currencies Empty array = all currencies. Some endpoints only support
|
||||
// singular currency enquiries
|
||||
Pairs []currency.Pair
|
||||
}
|
||||
|
||||
// Status defines order status types
|
||||
type Status string
|
||||
|
||||
// All order status types
|
||||
const (
|
||||
AnyStatus Status = "ANY"
|
||||
New Status = "NEW"
|
||||
Active Status = "ACTIVE"
|
||||
PartiallyCancelled Status = "PARTIALLY_CANCELLED"
|
||||
PartiallyFilled Status = "PARTIALLY_FILLED"
|
||||
Filled Status = "FILLED"
|
||||
Cancelled Status = "CANCELLED"
|
||||
PendingCancel Status = "PENDING_CANCEL"
|
||||
InsufficientBalance Status = "INSUFFICIENT_BALANCE"
|
||||
MarketUnavailable Status = "MARKET_UNAVAILABLE"
|
||||
Rejected Status = "REJECTED"
|
||||
Expired Status = "EXPIRED"
|
||||
Hidden Status = "HIDDEN"
|
||||
UnknownStatus Status = "UNKNOWN"
|
||||
)
|
||||
|
||||
// Type enforces a standard for order types across the code base
|
||||
type Type string
|
||||
|
||||
@@ -91,8 +218,8 @@ const (
|
||||
Market Type = "MARKET"
|
||||
ImmediateOrCancel Type = "IMMEDIATE_OR_CANCEL"
|
||||
Stop Type = "STOP"
|
||||
TrailingStop Type = "TRAILINGSTOP"
|
||||
Unknown Type = "UNKNOWN"
|
||||
TrailingStop Type = "TRAILING_STOP"
|
||||
UnknownType Type = "UNKNOWN"
|
||||
)
|
||||
|
||||
// Side enforces a standard for order sides across the code base
|
||||
@@ -105,78 +232,7 @@ const (
|
||||
Sell Side = "SELL"
|
||||
Bid Side = "BID"
|
||||
Ask Side = "ASK"
|
||||
SideUnknown Side = "SIDEUNKNOWN"
|
||||
)
|
||||
|
||||
// Detail holds order detail data
|
||||
type Detail struct {
|
||||
Exchange string
|
||||
AccountID string
|
||||
ID string
|
||||
CurrencyPair currency.Pair
|
||||
OrderSide Side
|
||||
OrderType Type
|
||||
OrderDate time.Time
|
||||
Status Status
|
||||
Price float64
|
||||
Amount float64
|
||||
ExecutedAmount float64
|
||||
RemainingAmount float64
|
||||
Fee float64
|
||||
Trades []TradeHistory
|
||||
}
|
||||
|
||||
// TradeHistory holds exchange history data
|
||||
type TradeHistory struct {
|
||||
Timestamp time.Time
|
||||
TID string
|
||||
Price float64
|
||||
Amount float64
|
||||
Exchange string
|
||||
Type Type
|
||||
Side Side
|
||||
Fee float64
|
||||
Description string
|
||||
}
|
||||
|
||||
// Cancel type required when requesting to cancel an order
|
||||
type Cancel struct {
|
||||
AccountID string
|
||||
OrderID string
|
||||
CurrencyPair currency.Pair
|
||||
AssetType asset.Item
|
||||
WalletAddress string
|
||||
Side Side
|
||||
}
|
||||
|
||||
// GetOrdersRequest used for GetOrderHistory and GetOpenOrders wrapper functions
|
||||
type GetOrdersRequest struct {
|
||||
OrderType Type
|
||||
OrderSide Side
|
||||
StartTicks time.Time
|
||||
EndTicks time.Time
|
||||
// Currencies Empty array = all currencies. Some endpoints only support
|
||||
// singular currency enquiries
|
||||
Currencies []currency.Pair
|
||||
}
|
||||
|
||||
// Status defines order status types
|
||||
type Status string
|
||||
|
||||
// All order status types
|
||||
const (
|
||||
AnyStatus Status = "ANY"
|
||||
New Status = "NEW"
|
||||
Active Status = "ACTIVE"
|
||||
PartiallyCancelled Status = "PARTIALLY_CANCELLED"
|
||||
PartiallyFilled Status = "PARTIALLY_FILLED"
|
||||
Filled Status = "FILLED"
|
||||
Cancelled Status = "CANCELLED"
|
||||
PendingCancel Status = "PENDING_CANCEL"
|
||||
Rejected Status = "REJECTED"
|
||||
Expired Status = "EXPIRED"
|
||||
Hidden Status = "HIDDEN"
|
||||
UnknownStatus Status = "UNKNOWN"
|
||||
UnknownSide Side = "UNKNOWN"
|
||||
)
|
||||
|
||||
// ByPrice used for sorting orders by price
|
||||
@@ -193,3 +249,23 @@ type ByDate []Detail
|
||||
|
||||
// ByOrderSide used for sorting orders by order side (buy sell)
|
||||
type ByOrderSide []Detail
|
||||
|
||||
// ClassificationError returned when an order status
|
||||
// side or type cannot be recognised
|
||||
type ClassificationError struct {
|
||||
Exchange string
|
||||
OrderID string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (o *ClassificationError) Error() string {
|
||||
if o.OrderID != "" {
|
||||
return fmt.Sprintf("%s - OrderID: %s classification error: %v",
|
||||
o.Exchange,
|
||||
o.OrderID,
|
||||
o.Err)
|
||||
}
|
||||
return fmt.Sprintf("%s - classification error: %v",
|
||||
o.Exchange,
|
||||
o.Err)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -9,57 +9,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
// NewOrder creates a new order and returns a an orderID
|
||||
func NewOrder(exchangeName string, amount, price float64) int {
|
||||
ord := &Order{}
|
||||
if len(Orders) == 0 {
|
||||
ord.OrderID = 0
|
||||
} else {
|
||||
ord.OrderID = len(Orders)
|
||||
}
|
||||
|
||||
ord.Exchange = exchangeName
|
||||
ord.Amount = amount
|
||||
ord.Price = price
|
||||
Orders = append(Orders, ord)
|
||||
return ord.OrderID
|
||||
}
|
||||
|
||||
// DeleteOrder deletes orders by ID and returns state
|
||||
func DeleteOrder(orderID int) bool {
|
||||
for i := range Orders {
|
||||
if Orders[i].OrderID == orderID {
|
||||
Orders = append(Orders[:i], Orders[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetOrdersByExchange returns order pointer grouped by exchange
|
||||
func GetOrdersByExchange(exchange string) []*Order {
|
||||
var orders []*Order
|
||||
for i := range Orders {
|
||||
if Orders[i].Exchange == exchange {
|
||||
orders = append(orders, Orders[i])
|
||||
}
|
||||
}
|
||||
if len(orders) > 0 {
|
||||
return orders
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrderByOrderID returns order pointer by ID
|
||||
func GetOrderByOrderID(orderID int) *Order {
|
||||
for i := range Orders {
|
||||
if Orders[i].OrderID == orderID {
|
||||
return Orders[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks the supplied data and returns whether or not it's valid
|
||||
func (s *Submit) Validate() error {
|
||||
if s == nil {
|
||||
@@ -70,14 +19,14 @@ func (s *Submit) Validate() error {
|
||||
return ErrPairIsEmpty
|
||||
}
|
||||
|
||||
if s.OrderSide != Buy &&
|
||||
s.OrderSide != Sell &&
|
||||
s.OrderSide != Bid &&
|
||||
s.OrderSide != Ask {
|
||||
if s.Side != Buy &&
|
||||
s.Side != Sell &&
|
||||
s.Side != Bid &&
|
||||
s.Side != Ask {
|
||||
return ErrSideIsInvalid
|
||||
}
|
||||
|
||||
if s.OrderType != Market && s.OrderType != Limit {
|
||||
if s.Type != Market && s.Type != Limit {
|
||||
return ErrTypeIsInvalid
|
||||
}
|
||||
|
||||
@@ -85,13 +34,311 @@ func (s *Submit) Validate() error {
|
||||
return ErrAmountIsInvalid
|
||||
}
|
||||
|
||||
if s.OrderType == Limit && s.Price <= 0 {
|
||||
if s.Type == Limit && s.Price <= 0 {
|
||||
return ErrPriceMustBeSetIfLimitOrder
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateOrderFromDetail Will update an order detail (used in order management)
|
||||
// by comparing passed in and existing values
|
||||
func (d *Detail) UpdateOrderFromDetail(m *Detail) {
|
||||
var updated bool
|
||||
if d.ImmediateOrCancel != m.ImmediateOrCancel {
|
||||
d.ImmediateOrCancel = m.ImmediateOrCancel
|
||||
updated = true
|
||||
}
|
||||
if d.HiddenOrder != m.HiddenOrder {
|
||||
d.HiddenOrder = m.HiddenOrder
|
||||
updated = true
|
||||
}
|
||||
if d.FillOrKill != m.FillOrKill {
|
||||
d.FillOrKill = m.FillOrKill
|
||||
updated = true
|
||||
}
|
||||
if m.Price > 0 && m.Price != d.Price {
|
||||
d.Price = m.Price
|
||||
updated = true
|
||||
}
|
||||
if m.Amount > 0 && m.Amount != d.Amount {
|
||||
d.Amount = m.Amount
|
||||
updated = true
|
||||
}
|
||||
if m.LimitPriceUpper > 0 && m.LimitPriceUpper != d.LimitPriceUpper {
|
||||
d.LimitPriceUpper = m.LimitPriceUpper
|
||||
updated = true
|
||||
}
|
||||
if m.LimitPriceLower > 0 && m.LimitPriceLower != d.LimitPriceLower {
|
||||
d.LimitPriceLower = m.LimitPriceLower
|
||||
updated = true
|
||||
}
|
||||
if m.TriggerPrice > 0 && m.TriggerPrice != d.TriggerPrice {
|
||||
d.TriggerPrice = m.TriggerPrice
|
||||
updated = true
|
||||
}
|
||||
if m.TargetAmount > 0 && m.TargetAmount != d.TargetAmount {
|
||||
d.TargetAmount = m.TargetAmount
|
||||
updated = true
|
||||
}
|
||||
if m.ExecutedAmount > 0 && m.ExecutedAmount != d.ExecutedAmount {
|
||||
d.ExecutedAmount = m.ExecutedAmount
|
||||
updated = true
|
||||
}
|
||||
if m.Fee > 0 && m.Fee != d.Fee {
|
||||
d.Fee = m.Fee
|
||||
updated = true
|
||||
}
|
||||
if m.AccountID != "" && m.AccountID != d.AccountID {
|
||||
d.AccountID = m.AccountID
|
||||
updated = true
|
||||
}
|
||||
if m.PostOnly != d.PostOnly {
|
||||
d.PostOnly = m.PostOnly
|
||||
updated = true
|
||||
}
|
||||
if !m.Pair.IsEmpty() && m.Pair != d.Pair {
|
||||
d.Pair = m.Pair
|
||||
updated = true
|
||||
}
|
||||
if m.Leverage != "" && m.Leverage != d.Leverage {
|
||||
d.Leverage = m.Leverage
|
||||
updated = true
|
||||
}
|
||||
if m.ClientID != "" && m.ClientID != d.ClientID {
|
||||
d.ClientID = m.ClientID
|
||||
updated = true
|
||||
}
|
||||
if m.WalletAddress != "" && m.WalletAddress != d.WalletAddress {
|
||||
d.WalletAddress = m.WalletAddress
|
||||
updated = true
|
||||
}
|
||||
if m.Type != "" && m.Type != d.Type {
|
||||
d.Type = m.Type
|
||||
updated = true
|
||||
}
|
||||
if m.Side != "" && m.Side != d.Side {
|
||||
d.Side = m.Side
|
||||
updated = true
|
||||
}
|
||||
if m.Status != "" && m.Status != d.Status {
|
||||
d.Status = m.Status
|
||||
updated = true
|
||||
}
|
||||
if m.AssetType != "" && m.AssetType != d.AssetType {
|
||||
d.AssetType = m.AssetType
|
||||
updated = true
|
||||
}
|
||||
if m.Trades != nil {
|
||||
for x := range m.Trades {
|
||||
var found bool
|
||||
for y := range d.Trades {
|
||||
if d.Trades[y].TID != m.Trades[x].TID {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
if d.Trades[y].Fee != m.Trades[x].Fee {
|
||||
d.Trades[y].Fee = m.Trades[x].Fee
|
||||
updated = true
|
||||
}
|
||||
if m.Trades[y].Price != 0 && d.Trades[y].Price != m.Trades[x].Price {
|
||||
d.Trades[y].Price = m.Trades[x].Price
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Side != m.Trades[x].Side {
|
||||
d.Trades[y].Side = m.Trades[x].Side
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Type != m.Trades[x].Type {
|
||||
d.Trades[y].Type = m.Trades[x].Type
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Description != m.Trades[x].Description {
|
||||
d.Trades[y].Description = m.Trades[x].Description
|
||||
updated = true
|
||||
}
|
||||
if m.Trades[y].Amount != 0 && d.Trades[y].Amount != m.Trades[x].Amount {
|
||||
d.Trades[y].Amount = m.Trades[x].Amount
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Timestamp != m.Trades[x].Timestamp {
|
||||
d.Trades[y].Timestamp = m.Trades[x].Timestamp
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].IsMaker != m.Trades[x].IsMaker {
|
||||
d.Trades[y].IsMaker = m.Trades[x].IsMaker
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
d.Trades = append(d.Trades, m.Trades[x])
|
||||
updated = true
|
||||
}
|
||||
m.RemainingAmount -= m.Trades[x].Amount
|
||||
}
|
||||
}
|
||||
if m.RemainingAmount > 0 && m.RemainingAmount != d.RemainingAmount {
|
||||
d.RemainingAmount = m.RemainingAmount
|
||||
updated = true
|
||||
}
|
||||
if updated {
|
||||
if d.LastUpdated == m.LastUpdated {
|
||||
d.LastUpdated = time.Now()
|
||||
} else {
|
||||
d.LastUpdated = m.LastUpdated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateOrderFromModify Will update an order detail (used in order management)
|
||||
// by comparing passed in and existing values
|
||||
func (d *Detail) UpdateOrderFromModify(m *Modify) {
|
||||
var updated bool
|
||||
if d.ImmediateOrCancel != m.ImmediateOrCancel {
|
||||
d.ImmediateOrCancel = m.ImmediateOrCancel
|
||||
updated = true
|
||||
}
|
||||
if d.HiddenOrder != m.HiddenOrder {
|
||||
d.HiddenOrder = m.HiddenOrder
|
||||
updated = true
|
||||
}
|
||||
if d.FillOrKill != m.FillOrKill {
|
||||
d.FillOrKill = m.FillOrKill
|
||||
updated = true
|
||||
}
|
||||
if m.Price > 0 && m.Price != d.Price {
|
||||
d.Price = m.Price
|
||||
updated = true
|
||||
}
|
||||
if m.Amount > 0 && m.Amount != d.Amount {
|
||||
d.Amount = m.Amount
|
||||
updated = true
|
||||
}
|
||||
if m.LimitPriceUpper > 0 && m.LimitPriceUpper != d.LimitPriceUpper {
|
||||
d.LimitPriceUpper = m.LimitPriceUpper
|
||||
updated = true
|
||||
}
|
||||
if m.LimitPriceLower > 0 && m.LimitPriceLower != d.LimitPriceLower {
|
||||
d.LimitPriceLower = m.LimitPriceLower
|
||||
updated = true
|
||||
}
|
||||
if m.TriggerPrice > 0 && m.TriggerPrice != d.TriggerPrice {
|
||||
d.TriggerPrice = m.TriggerPrice
|
||||
updated = true
|
||||
}
|
||||
if m.TargetAmount > 0 && m.TargetAmount != d.TargetAmount {
|
||||
d.TargetAmount = m.TargetAmount
|
||||
updated = true
|
||||
}
|
||||
if m.ExecutedAmount > 0 && m.ExecutedAmount != d.ExecutedAmount {
|
||||
d.ExecutedAmount = m.ExecutedAmount
|
||||
updated = true
|
||||
}
|
||||
if m.Fee > 0 && m.Fee != d.Fee {
|
||||
d.Fee = m.Fee
|
||||
updated = true
|
||||
}
|
||||
if m.AccountID != "" && m.AccountID != d.AccountID {
|
||||
d.AccountID = m.AccountID
|
||||
updated = true
|
||||
}
|
||||
if m.PostOnly != d.PostOnly {
|
||||
d.PostOnly = m.PostOnly
|
||||
updated = true
|
||||
}
|
||||
if !m.Pair.IsEmpty() && m.Pair != d.Pair {
|
||||
d.Pair = m.Pair
|
||||
updated = true
|
||||
}
|
||||
if m.Leverage != "" && m.Leverage != d.Leverage {
|
||||
d.Leverage = m.Leverage
|
||||
updated = true
|
||||
}
|
||||
if m.ClientID != "" && m.ClientID != d.ClientID {
|
||||
d.ClientID = m.ClientID
|
||||
updated = true
|
||||
}
|
||||
if m.WalletAddress != "" && m.WalletAddress != d.WalletAddress {
|
||||
d.WalletAddress = m.WalletAddress
|
||||
updated = true
|
||||
}
|
||||
if m.Type != "" && m.Type != d.Type {
|
||||
d.Type = m.Type
|
||||
updated = true
|
||||
}
|
||||
if m.Side != "" && m.Side != d.Side {
|
||||
d.Side = m.Side
|
||||
updated = true
|
||||
}
|
||||
if m.Status != "" && m.Status != d.Status {
|
||||
d.Status = m.Status
|
||||
updated = true
|
||||
}
|
||||
if m.AssetType != "" && m.AssetType != d.AssetType {
|
||||
d.AssetType = m.AssetType
|
||||
updated = true
|
||||
}
|
||||
if m.Trades != nil {
|
||||
for x := range m.Trades {
|
||||
var found bool
|
||||
for y := range d.Trades {
|
||||
if d.Trades[y].TID != m.Trades[x].TID {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
if d.Trades[y].Fee != m.Trades[x].Fee {
|
||||
d.Trades[y].Fee = m.Trades[x].Fee
|
||||
updated = true
|
||||
}
|
||||
if m.Trades[y].Price != 0 && d.Trades[y].Price != m.Trades[x].Price {
|
||||
d.Trades[y].Price = m.Trades[x].Price
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Side != m.Trades[x].Side {
|
||||
d.Trades[y].Side = m.Trades[x].Side
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Type != m.Trades[x].Type {
|
||||
d.Trades[y].Type = m.Trades[x].Type
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Description != m.Trades[x].Description {
|
||||
d.Trades[y].Description = m.Trades[x].Description
|
||||
updated = true
|
||||
}
|
||||
if m.Trades[y].Amount != 0 && d.Trades[y].Amount != m.Trades[x].Amount {
|
||||
d.Trades[y].Amount = m.Trades[x].Amount
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].Timestamp != m.Trades[x].Timestamp {
|
||||
d.Trades[y].Timestamp = m.Trades[x].Timestamp
|
||||
updated = true
|
||||
}
|
||||
if d.Trades[y].IsMaker != m.Trades[x].IsMaker {
|
||||
d.Trades[y].IsMaker = m.Trades[x].IsMaker
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
d.Trades = append(d.Trades, m.Trades[x])
|
||||
updated = true
|
||||
}
|
||||
m.RemainingAmount -= m.Trades[x].Amount
|
||||
}
|
||||
}
|
||||
if m.RemainingAmount > 0 && m.RemainingAmount != d.RemainingAmount {
|
||||
d.RemainingAmount = m.RemainingAmount
|
||||
updated = true
|
||||
}
|
||||
if updated {
|
||||
if d.LastUpdated == m.LastUpdated {
|
||||
d.LastUpdated = time.Now()
|
||||
} else {
|
||||
d.LastUpdated = m.LastUpdated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String implements the stringer interface
|
||||
func (t Type) String() string {
|
||||
return string(t)
|
||||
@@ -126,7 +373,7 @@ func FilterOrdersBySide(orders *[]Detail, side Side) {
|
||||
|
||||
var filteredOrders []Detail
|
||||
for i := range *orders {
|
||||
if strings.EqualFold(string((*orders)[i].OrderSide), string(side)) {
|
||||
if strings.EqualFold(string((*orders)[i].Side), string(side)) {
|
||||
filteredOrders = append(filteredOrders, (*orders)[i])
|
||||
}
|
||||
}
|
||||
@@ -143,7 +390,7 @@ func FilterOrdersByType(orders *[]Detail, orderType Type) {
|
||||
|
||||
var filteredOrders []Detail
|
||||
for i := range *orders {
|
||||
if strings.EqualFold(string((*orders)[i].OrderType), string(orderType)) {
|
||||
if strings.EqualFold(string((*orders)[i].Type), string(orderType)) {
|
||||
filteredOrders = append(filteredOrders, (*orders)[i])
|
||||
}
|
||||
}
|
||||
@@ -163,8 +410,8 @@ func FilterOrdersByTickRange(orders *[]Detail, startTicks, endTicks time.Time) {
|
||||
|
||||
var filteredOrders []Detail
|
||||
for i := range *orders {
|
||||
if (*orders)[i].OrderDate.Unix() >= startTicks.Unix() &&
|
||||
(*orders)[i].OrderDate.Unix() <= endTicks.Unix() {
|
||||
if (*orders)[i].Date.Unix() >= startTicks.Unix() &&
|
||||
(*orders)[i].Date.Unix() <= endTicks.Unix() {
|
||||
filteredOrders = append(filteredOrders, (*orders)[i])
|
||||
}
|
||||
}
|
||||
@@ -184,7 +431,7 @@ func FilterOrdersByCurrencies(orders *[]Detail, currencies []currency.Pair) {
|
||||
for i := range *orders {
|
||||
matchFound := false
|
||||
for _, c := range currencies {
|
||||
if !matchFound && (*orders)[i].CurrencyPair.EqualIncludeReciprocal(c) {
|
||||
if !matchFound && (*orders)[i].Pair.EqualIncludeReciprocal(c) {
|
||||
matchFound = true
|
||||
}
|
||||
}
|
||||
@@ -223,7 +470,7 @@ func (b ByOrderType) Len() int {
|
||||
}
|
||||
|
||||
func (b ByOrderType) Less(i, j int) bool {
|
||||
return b[i].OrderType.String() < b[j].OrderType.String()
|
||||
return b[i].Type.String() < b[j].Type.String()
|
||||
}
|
||||
|
||||
func (b ByOrderType) Swap(i, j int) {
|
||||
@@ -244,7 +491,7 @@ func (b ByCurrency) Len() int {
|
||||
}
|
||||
|
||||
func (b ByCurrency) Less(i, j int) bool {
|
||||
return b[i].CurrencyPair.String() < b[j].CurrencyPair.String()
|
||||
return b[i].Pair.String() < b[j].Pair.String()
|
||||
}
|
||||
|
||||
func (b ByCurrency) Swap(i, j int) {
|
||||
@@ -265,7 +512,7 @@ func (b ByDate) Len() int {
|
||||
}
|
||||
|
||||
func (b ByDate) Less(i, j int) bool {
|
||||
return b[i].OrderDate.Unix() < b[j].OrderDate.Unix()
|
||||
return b[i].Date.Unix() < b[j].Date.Unix()
|
||||
}
|
||||
|
||||
func (b ByDate) Swap(i, j int) {
|
||||
@@ -286,7 +533,7 @@ func (b ByOrderSide) Len() int {
|
||||
}
|
||||
|
||||
func (b ByOrderSide) Less(i, j int) bool {
|
||||
return b[i].OrderSide.String() < b[j].OrderSide.String()
|
||||
return b[i].Side.String() < b[j].Side.String()
|
||||
}
|
||||
|
||||
func (b ByOrderSide) Swap(i, j int) {
|
||||
@@ -317,7 +564,7 @@ func StringToOrderSide(side string) (Side, error) {
|
||||
case strings.EqualFold(side, AnySide.String()):
|
||||
return AnySide, nil
|
||||
default:
|
||||
return Side(""), fmt.Errorf("%s not recognised as side type", side)
|
||||
return UnknownSide, errors.New(side + " not recognised as order side")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,16 +576,20 @@ func StringToOrderType(oType string) (Type, error) {
|
||||
return Limit, nil
|
||||
case strings.EqualFold(oType, Market.String()):
|
||||
return Market, nil
|
||||
case strings.EqualFold(oType, ImmediateOrCancel.String()):
|
||||
case strings.EqualFold(oType, ImmediateOrCancel.String()),
|
||||
strings.EqualFold(oType, "immediate or cancel"):
|
||||
return ImmediateOrCancel, nil
|
||||
case strings.EqualFold(oType, Stop.String()):
|
||||
case strings.EqualFold(oType, Stop.String()),
|
||||
strings.EqualFold(oType, "stop loss"),
|
||||
strings.EqualFold(oType, "stop_loss"):
|
||||
return Stop, nil
|
||||
case strings.EqualFold(oType, TrailingStop.String()):
|
||||
case strings.EqualFold(oType, TrailingStop.String()),
|
||||
strings.EqualFold(oType, "trailing stop"):
|
||||
return TrailingStop, nil
|
||||
case strings.EqualFold(oType, AnyType.String()):
|
||||
return AnyType, nil
|
||||
default:
|
||||
return Unknown, fmt.Errorf("%s not recognised as order type", oType)
|
||||
return UnknownType, errors.New(oType + " not recognised as order type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,17 +599,27 @@ func StringToOrderStatus(status string) (Status, error) {
|
||||
switch {
|
||||
case strings.EqualFold(status, AnyStatus.String()):
|
||||
return AnyStatus, nil
|
||||
case strings.EqualFold(status, New.String()):
|
||||
case strings.EqualFold(status, New.String()),
|
||||
strings.EqualFold(status, "placed"):
|
||||
return New, nil
|
||||
case strings.EqualFold(status, Active.String()):
|
||||
return Active, nil
|
||||
case strings.EqualFold(status, PartiallyFilled.String()):
|
||||
case strings.EqualFold(status, PartiallyFilled.String()),
|
||||
strings.EqualFold(status, "partially matched"),
|
||||
strings.EqualFold(status, "partially filled"):
|
||||
return PartiallyFilled, nil
|
||||
case strings.EqualFold(status, Filled.String()):
|
||||
case strings.EqualFold(status, Filled.String()),
|
||||
strings.EqualFold(status, "fully matched"),
|
||||
strings.EqualFold(status, "fully filled"):
|
||||
return Filled, nil
|
||||
case strings.EqualFold(status, PartiallyCancelled.String()),
|
||||
strings.EqualFold(status, "partially cancelled"):
|
||||
return PartiallyCancelled, nil
|
||||
case strings.EqualFold(status, Cancelled.String()):
|
||||
return Cancelled, nil
|
||||
case strings.EqualFold(status, PendingCancel.String()):
|
||||
case strings.EqualFold(status, PendingCancel.String()),
|
||||
strings.EqualFold(status, "pending cancel"),
|
||||
strings.EqualFold(status, "pending cancellation"):
|
||||
return PendingCancel, nil
|
||||
case strings.EqualFold(status, Rejected.String()):
|
||||
return Rejected, nil
|
||||
@@ -366,7 +627,11 @@ func StringToOrderStatus(status string) (Status, error) {
|
||||
return Expired, nil
|
||||
case strings.EqualFold(status, Hidden.String()):
|
||||
return Hidden, nil
|
||||
case strings.EqualFold(status, InsufficientBalance.String()):
|
||||
return InsufficientBalance, nil
|
||||
case strings.EqualFold(status, MarketUnavailable.String()):
|
||||
return MarketUnavailable, nil
|
||||
default:
|
||||
return UnknownStatus, fmt.Errorf("%s not recognised as order STATUS", status)
|
||||
return UnknownStatus, errors.New(status + " not recognised as order status")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewOrder(t *testing.T) {
|
||||
ID := NewOrder("OKEX", 2000, 20.00)
|
||||
if ID != 0 {
|
||||
t.Error("Orders_test.go NewOrder() - Error")
|
||||
}
|
||||
ID = NewOrder("BATMAN", 400, 25.00)
|
||||
if ID != 1 {
|
||||
t.Error("Orders_test.go NewOrder() - Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteOrder(t *testing.T) {
|
||||
if value := DeleteOrder(0); !value {
|
||||
t.Error("Orders_test.go DeleteOrder() - Error")
|
||||
}
|
||||
if value := DeleteOrder(100); value {
|
||||
t.Error("Orders_test.go DeleteOrder() - Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrdersByExchange(t *testing.T) {
|
||||
if value := GetOrdersByExchange("OKEX"); len(value) != 0 {
|
||||
t.Error("Orders_test.go GetOrdersByExchange() - Error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderByOrderID(t *testing.T) {
|
||||
if value := GetOrderByOrderID(69); value != nil {
|
||||
t.Error("Orders_test.go GetOrdersByExchange() - Error")
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user