mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 23:16:49 +00:00
exchanges: Remove OKGroup (#1103)
* removes okgroup * fixes more things * pointers, string concat, comments + test fixes * lint * addresses nits without testing yet
This commit is contained in:
@@ -1,22 +1,783 @@
|
||||
package okcoin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
)
|
||||
|
||||
const (
|
||||
okCoinRateInterval = time.Second
|
||||
okCoinStandardRequestRate = 6
|
||||
okCoinAPIPath = "api/"
|
||||
okCoinAPIURL = "https://www.okcoin.com/" + okCoinAPIPath
|
||||
apiPath = "api/"
|
||||
okCoinAPIURL = "https://www.okcoin.com/" + apiPath
|
||||
okCoinAPIVersion = "/v3/"
|
||||
okCoinExchangeName = "OKCOIN International"
|
||||
okCoinWebsocketURL = "wss://real.okcoin.com:8443/ws/v3"
|
||||
)
|
||||
|
||||
// OKCoin bases all methods off okgroup implementation
|
||||
// OKCoin is the overarching type used for OKCoin's exchange API implementation
|
||||
type OKCoin struct {
|
||||
okgroup.OKGroup
|
||||
exchange.Base
|
||||
// Spot and contract market error codes
|
||||
ErrorCodes map[string]error
|
||||
}
|
||||
|
||||
const (
|
||||
accountSubsection = "account"
|
||||
tokenSubsection = "spot"
|
||||
marginTradingSubsection = "margin"
|
||||
accounts = "accounts"
|
||||
ledger = "ledger"
|
||||
orders = "orders"
|
||||
batchOrders = "batch_orders"
|
||||
cancelOrders = "cancel_orders"
|
||||
cancelBatchOrders = "cancel_batch_orders"
|
||||
pendingOrders = "orders_pending"
|
||||
trades = "trades"
|
||||
tickerData = "ticker"
|
||||
instruments = "instruments"
|
||||
getAccountDepositHistory = "deposit/history"
|
||||
getSpotTransactionDetails = "fills"
|
||||
getSpotOrderBook = "book"
|
||||
getSpotMarketData = "candles"
|
||||
// Account based endpoints
|
||||
getAccountCurrencies = "currencies"
|
||||
getAccountWalletInformation = "wallet"
|
||||
fundsTransfer = "transfer"
|
||||
withdrawRequest = "withdrawal"
|
||||
getWithdrawalFees = "withdrawal/fee"
|
||||
getWithdrawalHistory = "withdrawal/history"
|
||||
getDepositAddress = "deposit/address"
|
||||
// Margin based endpoints
|
||||
getMarketAvailability = "availability"
|
||||
getLoan = "borrow"
|
||||
getRepayment = "repayment"
|
||||
)
|
||||
|
||||
// GetAccountCurrencies returns a list of tradable spot instruments and their properties
|
||||
func (o *OKCoin) GetAccountCurrencies(ctx context.Context) ([]GetAccountCurrenciesResponse, error) {
|
||||
var respData []struct {
|
||||
Name string `json:"name"`
|
||||
Currency string `json:"currency"`
|
||||
Chain string `json:"chain"`
|
||||
CanInternal int64 `json:"can_internal,string"`
|
||||
CanWithdraw int64 `json:"can_withdraw,string"`
|
||||
CanDeposit int64 `json:"can_deposit,string"`
|
||||
MinWithdrawal string `json:"min_withdrawal"`
|
||||
}
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, accountSubsection, getAccountCurrencies, nil, &respData, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]GetAccountCurrenciesResponse, len(respData))
|
||||
for i := range respData {
|
||||
var mw float64
|
||||
if respData[i].MinWithdrawal != "" {
|
||||
mw, err = strconv.ParseFloat(respData[i].MinWithdrawal, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
resp[i] = GetAccountCurrenciesResponse{
|
||||
Name: respData[i].Name,
|
||||
Currency: respData[i].Currency,
|
||||
Chain: respData[i].Chain,
|
||||
CanInternal: respData[i].CanInternal == 1,
|
||||
CanWithdraw: respData[i].CanWithdraw == 1,
|
||||
CanDeposit: respData[i].CanDeposit == 1,
|
||||
MinWithdrawal: mw,
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetAccountWalletInformation returns a list of wallets and their properties
|
||||
func (o *OKCoin) GetAccountWalletInformation(ctx context.Context, currency string) ([]WalletInformationResponse, error) {
|
||||
requestURL := getAccountWalletInformation
|
||||
if currency != "" {
|
||||
requestURL += "/" + currency
|
||||
}
|
||||
var resp []WalletInformationResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, accountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// TransferAccountFunds the transfer of funds between wallet, trading accounts, main account and subaccounts
|
||||
func (o *OKCoin) TransferAccountFunds(ctx context.Context, request *TransferAccountFundsRequest) (*TransferAccountFundsResponse, error) {
|
||||
var resp *TransferAccountFundsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, accountSubsection, fundsTransfer, request, &resp, true)
|
||||
}
|
||||
|
||||
// AccountWithdraw withdrawal of tokens to OKCoin International or other addresses.
|
||||
func (o *OKCoin) AccountWithdraw(ctx context.Context, request *AccountWithdrawRequest) (*AccountWithdrawResponse, error) {
|
||||
var resp *AccountWithdrawResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, accountSubsection, withdrawRequest, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWithdrawalFee retrieves the information about the recommended network transaction fee for withdrawals to digital asset addresses. The higher the fees are, the sooner the confirmations you will get.
|
||||
func (o *OKCoin) GetAccountWithdrawalFee(ctx context.Context, currency string) ([]GetAccountWithdrawalFeeResponse, error) {
|
||||
requestURL := getWithdrawalFees
|
||||
if currency != "" {
|
||||
requestURL += "?currency=" + currency
|
||||
}
|
||||
|
||||
var resp []GetAccountWithdrawalFeeResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, accountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWithdrawalHistory retrieves all recent withdrawal records.
|
||||
func (o *OKCoin) GetAccountWithdrawalHistory(ctx context.Context, currency string) ([]WithdrawalHistoryResponse, error) {
|
||||
requestURL := getWithdrawalHistory
|
||||
if currency != "" {
|
||||
requestURL += "/" + currency
|
||||
}
|
||||
var resp []WithdrawalHistoryResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, accountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountBillDetails retrieves the bill details of the wallet. All the information will be paged and sorted in reverse chronological order,
|
||||
// which means the latest will be at the top. Please refer to the pagination section for additional records after the first page.
|
||||
// 3 months recent records will be returned at maximum
|
||||
func (o *OKCoin) GetAccountBillDetails(ctx context.Context, request *GetAccountBillDetailsRequest) ([]GetAccountBillDetailsResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := ledger + encodedRequest
|
||||
var resp []GetAccountBillDetailsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, accountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountDepositAddressForCurrency retrieves the deposit addresses of different tokens, including previously used addresses.
|
||||
func (o *OKCoin) GetAccountDepositAddressForCurrency(ctx context.Context, currency string) ([]GetDepositAddressResponse, error) {
|
||||
urlValues := url.Values{}
|
||||
urlValues.Set("currency", currency)
|
||||
requestURL := getDepositAddress + "?" + urlValues.Encode()
|
||||
var resp []GetDepositAddressResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, accountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountDepositHistory retrieves the deposit history of all tokens.100 recent records will be returned at maximum
|
||||
func (o *OKCoin) GetAccountDepositHistory(ctx context.Context, currency string) ([]GetAccountDepositHistoryResponse, error) {
|
||||
requestURL := getAccountDepositHistory
|
||||
if currency != "" {
|
||||
requestURL += "/" + currency
|
||||
}
|
||||
var resp []GetAccountDepositHistoryResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, accountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTradingAccounts retrieves the list of assets(only show pairs with balance larger than 0), the balances, amount available/on hold in spot accounts.
|
||||
func (o *OKCoin) GetSpotTradingAccounts(ctx context.Context) ([]GetSpotTradingAccountResponse, error) {
|
||||
var resp []GetSpotTradingAccountResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, accounts, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTradingAccountForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account.
|
||||
func (o *OKCoin) GetSpotTradingAccountForCurrency(ctx context.Context, currency string) (*GetSpotTradingAccountResponse, error) {
|
||||
requestURL := accounts + "/" + currency
|
||||
var resp *GetSpotTradingAccountResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotBillDetailsForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account.
|
||||
func (o *OKCoin) GetSpotBillDetailsForCurrency(ctx context.Context, request *GetSpotBillDetailsForCurrencyRequest) ([]GetSpotBillDetailsForCurrencyResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := accounts + "/" + request.Currency + "/" + ledger + encodedRequest
|
||||
var resp []GetSpotBillDetailsForCurrencyResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceSpotOrder token trading only supports limit and market orders (more order types will become available in the future).
|
||||
// You can place an order only if you have enough funds.
|
||||
// Once your order is placed, the amount will be put on hold.
|
||||
func (o *OKCoin) PlaceSpotOrder(ctx context.Context, request *PlaceOrderRequest) (*PlaceOrderResponse, error) {
|
||||
if request.OrderType == "" {
|
||||
request.OrderType = "0"
|
||||
}
|
||||
var resp *PlaceOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, tokenSubsection, orders, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMultipleSpotOrders supports placing multiple orders for specific trading pairs
|
||||
// up to 4 trading pairs, maximum 4 orders for each pair
|
||||
func (o *OKCoin) PlaceMultipleSpotOrders(ctx context.Context, request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) {
|
||||
currencyPairOrders := make(map[string]int)
|
||||
resp := make(map[string][]PlaceOrderResponse)
|
||||
|
||||
for i := range request {
|
||||
if request[i].OrderType == "" {
|
||||
request[i].OrderType = "0"
|
||||
}
|
||||
currencyPairOrders[request[i].InstrumentID]++
|
||||
}
|
||||
|
||||
if len(currencyPairOrders) > 4 {
|
||||
return resp, []error{errors.New("up to 4 trading pairs")}
|
||||
}
|
||||
for _, orderCount := range currencyPairOrders {
|
||||
if orderCount > 4 {
|
||||
return resp, []error{errors.New("maximum 4 orders for each pair")}
|
||||
}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, tokenSubsection, batchOrders, request, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
var orderErrors []error
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
if !orderResponse[i].Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// CancelSpotOrder Cancelling an unfilled order.
|
||||
func (o *OKCoin) CancelSpotOrder(ctx context.Context, request *CancelSpotOrderRequest) (*CancelSpotOrderResponse, error) {
|
||||
requestURL := cancelOrders + "/" + strconv.FormatInt(request.OrderID, 10)
|
||||
var resp *CancelSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, tokenSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// CancelMultipleSpotOrders Cancelling multiple unfilled orders.
|
||||
func (o *OKCoin) CancelMultipleSpotOrders(ctx context.Context, request *CancelMultipleSpotOrdersRequest) (map[string][]CancelMultipleSpotOrdersResponse, error) {
|
||||
if len(request.OrderIDs) > 4 {
|
||||
return nil, errors.New("maximum 4 order cancellations for each pair")
|
||||
}
|
||||
|
||||
resp := make(map[string][]CancelMultipleSpotOrdersResponse)
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, tokenSubsection, cancelBatchOrders, []CancelMultipleSpotOrdersRequest{*request}, &resp, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
cancellationResponse := CancelMultipleSpotOrdersResponse{
|
||||
OrderID: orderResponse[i].OrderID,
|
||||
Result: orderResponse[i].Result,
|
||||
ClientOID: orderResponse[i].ClientOID,
|
||||
}
|
||||
|
||||
if !orderResponse[i].Result {
|
||||
cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency)
|
||||
}
|
||||
|
||||
resp[currency] = append(resp[currency], cancellationResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSpotOrders List your orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetSpotOrders(ctx context.Context, request *GetSpotOrdersRequest) ([]GetSpotOrderResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := orders + encodedRequest
|
||||
var resp []GetSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotOpenOrders List all your current open orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetSpotOpenOrders(ctx context.Context, request *GetSpotOpenOrdersRequest) ([]GetSpotOrderResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := pendingOrders + encodedRequest
|
||||
var resp []GetSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotOrder Get order details by order ID.
|
||||
func (o *OKCoin) GetSpotOrder(ctx context.Context, request *GetSpotOrderRequest) (*GetSpotOrderResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := orders + "/" + request.OrderID + encodedRequest
|
||||
var resp *GetSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTransactionDetails Get details of the recent filled orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetSpotTransactionDetails(ctx context.Context, request *GetSpotTransactionDetailsRequest) ([]GetSpotTransactionDetailsResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := getSpotTransactionDetails + encodedRequest
|
||||
var resp []GetSpotTransactionDetailsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTokenPairDetails Get market data. This endpoint provides the snapshots of market data and can be used without verifications.
|
||||
// List trading pairs and get the trading limit, price, and more information of different trading pairs.
|
||||
func (o *OKCoin) GetSpotTokenPairDetails(ctx context.Context) ([]GetSpotTokenPairDetailsResponse, error) {
|
||||
var resp []GetSpotTokenPairDetailsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, instruments, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetOrderBook Getting the order book of a trading pair. Pagination is not
|
||||
// supported here. The whole book will be returned for one request. Websocket is
|
||||
// recommended here.
|
||||
func (o *OKCoin) GetOrderBook(ctx context.Context, request *GetOrderBookRequest, a asset.Item) (*GetOrderBookResponse, error) {
|
||||
var resp *GetOrderBookResponse
|
||||
if a != asset.Spot {
|
||||
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
|
||||
}
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := instruments + "/" + request.InstrumentID + "/" + getSpotOrderBook + encodedRequest
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotAllTokenPairsInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all trading pairs.
|
||||
func (o *OKCoin) GetSpotAllTokenPairsInformation(ctx context.Context) ([]GetSpotTokenPairsInformationResponse, error) {
|
||||
requestURL := instruments + "/" + tickerData
|
||||
var resp []GetSpotTokenPairsInformationResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotAllTokenPairsInformationForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of a currency
|
||||
func (o *OKCoin) GetSpotAllTokenPairsInformationForCurrency(ctx context.Context, currency string) (*GetSpotTokenPairsInformationResponse, error) {
|
||||
requestURL := instruments + "/" + currency + "/" + tickerData
|
||||
var resp *GetSpotTokenPairsInformationResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotFilledOrdersInformation Get the recent 60 transactions of all trading pairs.
|
||||
// Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetSpotFilledOrdersInformation(ctx context.Context, request *GetSpotFilledOrdersInformationRequest) ([]GetSpotFilledOrdersInformationResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := instruments + "/" + request.InstrumentID + "/" + trades + encodedRequest
|
||||
var resp []GetSpotFilledOrdersInformationResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity.
|
||||
func (o *OKCoin) GetMarketData(ctx context.Context, request *GetMarketDataRequest) ([]kline.Candle, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := instruments + "/" + request.InstrumentID + "/" + getSpotMarketData + encodedRequest
|
||||
if request.Asset != asset.Spot && request.Asset != asset.Margin {
|
||||
return nil, asset.ErrNotSupported
|
||||
}
|
||||
var resp []interface{}
|
||||
err = o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, tokenSubsection, requestURL, nil, &resp, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
candles := make([]kline.Candle, len(resp))
|
||||
for x := range resp {
|
||||
t, ok := resp[x].([]interface{})
|
||||
if !ok {
|
||||
return nil, common.GetAssertError("[]interface{}", resp[x])
|
||||
}
|
||||
if len(t) < 6 {
|
||||
return nil, fmt.Errorf("%w expteced %v received %v", errIncorrectCandleDataLength, 6, len(t))
|
||||
}
|
||||
v, ok := t[0].(string)
|
||||
if !ok {
|
||||
return nil, common.GetAssertError("string", t[0])
|
||||
}
|
||||
var tempCandle kline.Candle
|
||||
if tempCandle.Time, err = time.Parse(time.RFC3339, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tempCandle.Open, err = convert.FloatFromString(t[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tempCandle.High, err = convert.FloatFromString(t[2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tempCandle.Low, err = convert.FloatFromString(t[3]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tempCandle.Close, err = convert.FloatFromString(t[4]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tempCandle.Volume, err = convert.FloatFromString(t[5]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
candles[x] = tempCandle
|
||||
}
|
||||
return candles, nil
|
||||
}
|
||||
|
||||
// GetMarginTradingAccounts List all assets under token margin trading account, including information such as balance, amount on hold and more.
|
||||
func (o *OKCoin) GetMarginTradingAccounts(ctx context.Context) ([]GetMarginAccountsResponse, error) {
|
||||
var resp []GetMarginAccountsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, accounts, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginTradingAccountsForCurrency Get the balance, amount on hold and more useful information.
|
||||
func (o *OKCoin) GetMarginTradingAccountsForCurrency(ctx context.Context, currency string) (*GetMarginAccountsResponse, error) {
|
||||
requestURL := accounts + "/" + currency
|
||||
var resp *GetMarginAccountsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginBillDetails List all bill details. Pagination is used here.
|
||||
// before and after cursor arguments should not be confused with before and after in chronological time.
|
||||
// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetMarginBillDetails(ctx context.Context, request *GetMarginBillDetailsRequest) ([]GetSpotBillDetailsForCurrencyResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := accounts + "/" + request.InstrumentID + "/" + ledger + encodedRequest
|
||||
var resp []GetSpotBillDetailsForCurrencyResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginAccountSettings Get all information of the margin trading account,
|
||||
// including the maximum loan amount, interest rate, and maximum leverage.
|
||||
func (o *OKCoin) GetMarginAccountSettings(ctx context.Context, currency string) ([]GetMarginAccountSettingsResponse, error) {
|
||||
requestURL := accounts + "/" + getMarketAvailability
|
||||
if currency != "" {
|
||||
requestURL = accounts + "/" + currency + "/" + getMarketAvailability
|
||||
}
|
||||
var resp []GetMarginAccountSettingsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginLoanHistory Get loan history of the margin trading account.
|
||||
// Pagination is used here. before and after cursor arguments should not be confused with before and after in chronological time.
|
||||
// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetMarginLoanHistory(ctx context.Context, request *GetMarginLoanHistoryRequest) ([]GetMarginLoanHistoryResponse, error) {
|
||||
requestURL := accounts + "/" + getLoan
|
||||
if len(request.InstrumentID) > 0 {
|
||||
requestURL = accounts + "/" + request.InstrumentID + "/" + getLoan
|
||||
}
|
||||
var resp []GetMarginLoanHistoryResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// OpenMarginLoan Borrowing tokens in a margin trading account.
|
||||
func (o *OKCoin) OpenMarginLoan(ctx context.Context, request *OpenMarginLoanRequest) (*OpenMarginLoanResponse, error) {
|
||||
requestURL := accounts + "/" + getLoan
|
||||
var resp *OpenMarginLoanResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, marginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// RepayMarginLoan Repaying tokens in a margin trading account.
|
||||
func (o *OKCoin) RepayMarginLoan(ctx context.Context, request *RepayMarginLoanRequest) (*RepayMarginLoanResponse, error) {
|
||||
requestURL := accounts + "/" + getRepayment
|
||||
var resp *RepayMarginLoanResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, marginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMarginOrder You can place an order only if you have enough funds. Once your order is placed, the amount will be put on hold.
|
||||
func (o *OKCoin) PlaceMarginOrder(ctx context.Context, request *PlaceOrderRequest) (*PlaceOrderResponse, error) {
|
||||
var resp *PlaceOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, marginTradingSubsection, orders, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMultipleMarginOrders Place multiple orders for specific trading pairs (up to 4 trading pairs, maximum 4 orders each)
|
||||
func (o *OKCoin) PlaceMultipleMarginOrders(ctx context.Context, request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) {
|
||||
currencyPairOrders := make(map[string]int)
|
||||
resp := make(map[string][]PlaceOrderResponse)
|
||||
for i := range request {
|
||||
currencyPairOrders[request[i].InstrumentID]++
|
||||
}
|
||||
if len(currencyPairOrders) > 4 {
|
||||
return resp, []error{errors.New("up to 4 trading pairs")}
|
||||
}
|
||||
for _, orderCount := range currencyPairOrders {
|
||||
if orderCount > 4 {
|
||||
return resp, []error{errors.New("maximum 4 orders for each pair")}
|
||||
}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, marginTradingSubsection, batchOrders, request, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
var orderErrors []error
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
if !orderResponse[i].Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// CancelMarginOrder Cancelling an unfilled order.
|
||||
func (o *OKCoin) CancelMarginOrder(ctx context.Context, request *CancelSpotOrderRequest) (*CancelSpotOrderResponse, error) {
|
||||
requestURL := cancelOrders + "/" + strconv.FormatInt(request.OrderID, 10)
|
||||
var resp *CancelSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, marginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// CancelMultipleMarginOrders Cancelling multiple unfilled orders.
|
||||
func (o *OKCoin) CancelMultipleMarginOrders(ctx context.Context, request *CancelMultipleSpotOrdersRequest) (map[string][]CancelMultipleSpotOrdersResponse, []error) {
|
||||
resp := make(map[string][]CancelMultipleSpotOrdersResponse)
|
||||
if len(request.OrderIDs) > 4 {
|
||||
return resp, []error{errors.New("maximum 4 order cancellations for each pair")}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, marginTradingSubsection, cancelBatchOrders, []CancelMultipleSpotOrdersRequest{*request}, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
var orderErrors []error
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
if !orderResponse[i].Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// GetMarginOrders List your orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetMarginOrders(ctx context.Context, request *GetSpotOrdersRequest) ([]GetSpotOrderResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := orders + encodedRequest
|
||||
var resp []GetSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginOpenOrders List all your current open orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetMarginOpenOrders(ctx context.Context, request *GetSpotOpenOrdersRequest) ([]GetSpotOrderResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := pendingOrders + encodedRequest
|
||||
var resp []GetSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginOrder Get order details by order ID.
|
||||
func (o *OKCoin) GetMarginOrder(ctx context.Context, request *GetSpotOrderRequest) (*GetSpotOrderResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := orders + "/" + request.OrderID + encodedRequest
|
||||
var resp *GetSpotOrderResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginTransactionDetails Get details of the recent filled orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKCoin) GetMarginTransactionDetails(ctx context.Context, request *GetSpotTransactionDetailsRequest) ([]GetSpotTransactionDetailsResponse, error) {
|
||||
encodedRequest, err := encodeRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL := getSpotTransactionDetails + encodedRequest
|
||||
var resp []GetSpotTransactionDetailsResponse
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// encodeRequest Formats URL parameters, useful for optional parameters due to OKCoin signature check
|
||||
func encodeRequest(request interface{}) (string, error) {
|
||||
v, err := query.Values(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp := v.Encode()
|
||||
if resp == "" {
|
||||
return resp, nil
|
||||
}
|
||||
return "?" + resp, nil
|
||||
}
|
||||
|
||||
// GetErrorCode returns an error code
|
||||
func (o *OKCoin) GetErrorCode(code interface{}) error {
|
||||
var assertedCode string
|
||||
|
||||
switch d := code.(type) {
|
||||
case float64:
|
||||
assertedCode = strconv.FormatFloat(d, 'f', -1, 64)
|
||||
case string:
|
||||
assertedCode = d
|
||||
default:
|
||||
return errors.New("unusual type returned")
|
||||
}
|
||||
|
||||
if i, ok := o.ErrorCodes[assertedCode]; ok {
|
||||
return i
|
||||
}
|
||||
return errors.New("unable to find SPOT error code")
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an authenticated http request to a desired
|
||||
// path with a JSON payload (of present)
|
||||
// URL arguments must be in the request path and not as url.URL values
|
||||
func (o *OKCoin) SendHTTPRequest(ctx context.Context, ep exchange.URL, httpMethod, requestType, requestPath string, data, result interface{}, authenticated bool) error {
|
||||
endpoint, err := o.API.Endpoints.GetURL(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var intermediary json.RawMessage
|
||||
newRequest := func() (*request.Item, error) {
|
||||
utcTime := time.Now().UTC().Format(time.RFC3339)
|
||||
payload := []byte("")
|
||||
|
||||
if data != nil {
|
||||
payload, err = json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
path := endpoint + requestType + okCoinAPIVersion + requestPath
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/json"
|
||||
if authenticated {
|
||||
var creds *account.Credentials
|
||||
creds, err = o.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signPath := "/" + apiPath + requestType + okCoinAPIVersion + requestPath
|
||||
|
||||
var hmac []byte
|
||||
hmac, err = crypto.GetHMAC(crypto.HashSHA256,
|
||||
[]byte(utcTime+httpMethod+signPath+string(payload)),
|
||||
[]byte(creds.Secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers["OK-ACCESS-KEY"] = creds.Key
|
||||
headers["OK-ACCESS-SIGN"] = crypto.Base64Encode(hmac)
|
||||
headers["OK-ACCESS-TIMESTAMP"] = utcTime
|
||||
headers["OK-ACCESS-PASSPHRASE"] = creds.ClientID
|
||||
}
|
||||
|
||||
return &request.Item{
|
||||
Method: strings.ToUpper(httpMethod),
|
||||
Path: path,
|
||||
Headers: headers,
|
||||
Body: bytes.NewBuffer(payload),
|
||||
Result: &intermediary,
|
||||
AuthRequest: authenticated,
|
||||
Verbose: o.Verbose,
|
||||
HTTPDebugging: o.HTTPDebugging,
|
||||
HTTPRecording: o.HTTPRecording,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err = o.SendPayload(ctx, request.Unset, newRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type errCapFormat struct {
|
||||
Error int64 `json:"error_code"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
Result bool `json:"result,string"`
|
||||
}
|
||||
errCap := errCapFormat{Result: true}
|
||||
|
||||
err = json.Unmarshal(intermediary, &errCap)
|
||||
if err == nil {
|
||||
if errCap.ErrorMessage != "" {
|
||||
return fmt.Errorf("error: %v", errCap.ErrorMessage)
|
||||
}
|
||||
if errCap.Error > 0 {
|
||||
return fmt.Errorf("sendHTTPRequest error - %s",
|
||||
o.ErrorCodes[strconv.FormatInt(errCap.Error, 10)])
|
||||
}
|
||||
if !errCap.Result {
|
||||
return errors.New("unspecified error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
return json.Unmarshal(intermediary, result)
|
||||
}
|
||||
|
||||
// GetFee returns an estimate of fee based on type of transaction
|
||||
func (o *OKCoin) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
var fee float64
|
||||
switch feeBuilder.FeeType {
|
||||
case exchange.CryptocurrencyTradeFee:
|
||||
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker)
|
||||
case exchange.CryptocurrencyWithdrawalFee:
|
||||
withdrawFees, err := o.GetAccountWithdrawalFee(ctx, feeBuilder.FiatCurrency.String())
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
for _, withdrawFee := range withdrawFees {
|
||||
if withdrawFee.Currency == feeBuilder.FiatCurrency.String() {
|
||||
fee = withdrawFee.MinFee
|
||||
break
|
||||
}
|
||||
}
|
||||
case exchange.OfflineTradeFee:
|
||||
fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
|
||||
}
|
||||
if fee < 0 {
|
||||
fee = 0
|
||||
}
|
||||
|
||||
return fee, nil
|
||||
}
|
||||
|
||||
// getOfflineTradeFee calculates the worst case-scenario trading fee
|
||||
func getOfflineTradeFee(price, amount float64) float64 {
|
||||
return 0.0015 * price * amount
|
||||
}
|
||||
|
||||
func calculateTradingFee(purchasePrice, amount float64, isMaker bool) (fee float64) {
|
||||
// TODO volume based fees
|
||||
if isMaker {
|
||||
fee = 0.0005
|
||||
} else {
|
||||
fee = 0.0015
|
||||
}
|
||||
return fee * amount * purchasePrice
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,18 +1,17 @@
|
||||
package okgroup
|
||||
package okcoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// Order types
|
||||
const (
|
||||
NormalOrder = iota
|
||||
PostOnlyOrder
|
||||
FillOrKillOrder
|
||||
ImmediateOrCancelOrder
|
||||
var (
|
||||
errNoAccountDepositAddress = errors.New("no account deposit address")
|
||||
errIncorrectCandleDataLength = errors.New("incorrect candles data length")
|
||||
)
|
||||
|
||||
// PerpSwapInstrumentData stores instrument data for perpetual swap contracts
|
||||
@@ -106,9 +105,11 @@ type PerpSwapFundingRates struct {
|
||||
type GetAccountCurrenciesResponse struct {
|
||||
Name string `json:"name"`
|
||||
Currency string `json:"currency"`
|
||||
CanDeposit int `json:"can_deposit,string"`
|
||||
CanWithdraw int `json:"can_withdraw,string"`
|
||||
MinWithdrawal float64 `json:"min_withdrawal,string"`
|
||||
Chain string `json:"chain"`
|
||||
CanInternal bool `json:"can_internal,string"`
|
||||
CanWithdraw bool `json:"can_withdraw,string"`
|
||||
CanDeposit bool `json:"can_deposit,string"`
|
||||
MinWithdrawal float64 `json:"min_withdrawal"`
|
||||
}
|
||||
|
||||
// WalletInformationResponse response data for WalletInformation
|
||||
@@ -435,7 +436,8 @@ type GetMarketDataRequest struct {
|
||||
// low string Lowest price
|
||||
// close string Close price
|
||||
// volume string Trading volume
|
||||
type GetMarketDataResponse []interface{}
|
||||
type GetMarketDataResponse struct {
|
||||
}
|
||||
|
||||
// GetMarginAccountsResponse response data for GetMarginAccounts
|
||||
type GetMarginAccountsResponse struct {
|
||||
@@ -1354,7 +1356,7 @@ type GetETTSettlementPriceHistoryResponse struct {
|
||||
Price float64 `json:"price"`
|
||||
}
|
||||
|
||||
// OrderStatus Holds OKGroup order status values
|
||||
// OrderStatus Holds Okcoin order status values
|
||||
var OrderStatus = map[int64]string{
|
||||
-3: "pending cancel",
|
||||
-2: "cancelled",
|
||||
@@ -1517,3 +1519,294 @@ type WebsocketErrorResponse struct {
|
||||
Message string `json:"message"`
|
||||
ErrorCode int64 `json:"errorCode"`
|
||||
}
|
||||
|
||||
// List of all websocket channels to subscribe to
|
||||
const (
|
||||
// Orderbook events
|
||||
okcoinWsOrderbookUpdate = "update"
|
||||
okcoinWsOrderbookPartial = "partial"
|
||||
// API subsections
|
||||
okcoinWsSwapSubsection = "swap/"
|
||||
okcoinWsIndexSubsection = "index/"
|
||||
okcoinWsFuturesSubsection = "futures/"
|
||||
okcoinWsSpotSubsection = "spot/"
|
||||
// Shared API endpoints
|
||||
okcoinWsCandle = "candle"
|
||||
okcoinWsCandle60s = okcoinWsCandle + "60s"
|
||||
okcoinWsCandle180s = okcoinWsCandle + "180s"
|
||||
okcoinWsCandle300s = okcoinWsCandle + "300s"
|
||||
okcoinWsCandle900s = okcoinWsCandle + "900s"
|
||||
okcoinWsCandle1800s = okcoinWsCandle + "1800s"
|
||||
okcoinWsCandle3600s = okcoinWsCandle + "3600s"
|
||||
okcoinWsCandle7200s = okcoinWsCandle + "7200s"
|
||||
okcoinWsCandle14400s = okcoinWsCandle + "14400s"
|
||||
okcoinWsCandle21600s = okcoinWsCandle + "21600"
|
||||
okcoinWsCandle43200s = okcoinWsCandle + "43200s"
|
||||
okcoinWsCandle86400s = okcoinWsCandle + "86400s"
|
||||
okcoinWsCandle604900s = okcoinWsCandle + "604800s"
|
||||
okcoinWsTicker = "ticker"
|
||||
okcoinWsTrade = "trade"
|
||||
okcoinWsDepth = "depth"
|
||||
okcoinWsDepth5 = "depth5"
|
||||
okcoinWsAccount = "account"
|
||||
okcoinWsMarginAccount = "margin_account"
|
||||
okcoinWsOrder = "order"
|
||||
okcoinWsFundingRate = "funding_rate"
|
||||
okcoinWsPriceRange = "price_range"
|
||||
okcoinWsMarkPrice = "mark_price"
|
||||
okcoinWsPosition = "position"
|
||||
okcoinWsEstimatedPrice = "estimated_price"
|
||||
// Spot endpoints
|
||||
okcoinWsSpotTicker = okcoinWsSpotSubsection + okcoinWsTicker
|
||||
okcoinWsSpotCandle60s = okcoinWsSpotSubsection + okcoinWsCandle60s
|
||||
okcoinWsSpotCandle180s = okcoinWsSpotSubsection + okcoinWsCandle180s
|
||||
okcoinWsSpotCandle300s = okcoinWsSpotSubsection + okcoinWsCandle300s
|
||||
okcoinWsSpotCandle900s = okcoinWsSpotSubsection + okcoinWsCandle900s
|
||||
okcoinWsSpotCandle1800s = okcoinWsSpotSubsection + okcoinWsCandle1800s
|
||||
okcoinWsSpotCandle3600s = okcoinWsSpotSubsection + okcoinWsCandle3600s
|
||||
okcoinWsSpotCandle7200s = okcoinWsSpotSubsection + okcoinWsCandle7200s
|
||||
okcoinWsSpotCandle14400s = okcoinWsSpotSubsection + okcoinWsCandle14400s
|
||||
okcoinWsSpotCandle21600s = okcoinWsSpotSubsection + okcoinWsCandle21600s
|
||||
okcoinWsSpotCandle43200s = okcoinWsSpotSubsection + okcoinWsCandle43200s
|
||||
okcoinWsSpotCandle86400s = okcoinWsSpotSubsection + okcoinWsCandle86400s
|
||||
okcoinWsSpotCandle604900s = okcoinWsSpotSubsection + okcoinWsCandle604900s
|
||||
okcoinWsSpotTrade = okcoinWsSpotSubsection + okcoinWsTrade
|
||||
okcoinWsSpotDepth = okcoinWsSpotSubsection + okcoinWsDepth
|
||||
okcoinWsSpotDepth5 = okcoinWsSpotSubsection + okcoinWsDepth5
|
||||
okcoinWsSpotAccount = okcoinWsSpotSubsection + okcoinWsAccount
|
||||
okcoinWsSpotMarginAccount = okcoinWsSpotSubsection + okcoinWsMarginAccount
|
||||
okcoinWsSpotOrder = okcoinWsSpotSubsection + okcoinWsOrder
|
||||
// Swap endpoints
|
||||
okcoinWsSwapTicker = okcoinWsSwapSubsection + okcoinWsTicker
|
||||
okcoinWsSwapCandle60s = okcoinWsSwapSubsection + okcoinWsCandle60s
|
||||
okcoinWsSwapCandle180s = okcoinWsSwapSubsection + okcoinWsCandle180s
|
||||
okcoinWsSwapCandle300s = okcoinWsSwapSubsection + okcoinWsCandle300s
|
||||
okcoinWsSwapCandle900s = okcoinWsSwapSubsection + okcoinWsCandle900s
|
||||
okcoinWsSwapCandle1800s = okcoinWsSwapSubsection + okcoinWsCandle1800s
|
||||
okcoinWsSwapCandle3600s = okcoinWsSwapSubsection + okcoinWsCandle3600s
|
||||
okcoinWsSwapCandle7200s = okcoinWsSwapSubsection + okcoinWsCandle7200s
|
||||
okcoinWsSwapCandle14400s = okcoinWsSwapSubsection + okcoinWsCandle14400s
|
||||
okcoinWsSwapCandle21600s = okcoinWsSwapSubsection + okcoinWsCandle21600s
|
||||
okcoinWsSwapCandle43200s = okcoinWsSwapSubsection + okcoinWsCandle43200s
|
||||
okcoinWsSwapCandle86400s = okcoinWsSwapSubsection + okcoinWsCandle86400s
|
||||
okcoinWsSwapCandle604900s = okcoinWsSwapSubsection + okcoinWsCandle604900s
|
||||
okcoinWsSwapTrade = okcoinWsSwapSubsection + okcoinWsTrade
|
||||
okcoinWsSwapDepth = okcoinWsSwapSubsection + okcoinWsDepth
|
||||
okcoinWsSwapDepth5 = okcoinWsSwapSubsection + okcoinWsDepth5
|
||||
okcoinWsSwapFundingRate = okcoinWsSwapSubsection + okcoinWsFundingRate
|
||||
okcoinWsSwapPriceRange = okcoinWsSwapSubsection + okcoinWsPriceRange
|
||||
okcoinWsSwapMarkPrice = okcoinWsSwapSubsection + okcoinWsMarkPrice
|
||||
okcoinWsSwapPosition = okcoinWsSwapSubsection + okcoinWsPosition
|
||||
okcoinWsSwapAccount = okcoinWsSwapSubsection + okcoinWsAccount
|
||||
okcoinWsSwapOrder = okcoinWsSwapSubsection + okcoinWsOrder
|
||||
// Index endpoints
|
||||
okcoinWsIndexTicker = okcoinWsIndexSubsection + okcoinWsTicker
|
||||
okcoinWsIndexCandle60s = okcoinWsIndexSubsection + okcoinWsCandle60s
|
||||
okcoinWsIndexCandle180s = okcoinWsIndexSubsection + okcoinWsCandle180s
|
||||
okcoinWsIndexCandle300s = okcoinWsIndexSubsection + okcoinWsCandle300s
|
||||
okcoinWsIndexCandle900s = okcoinWsIndexSubsection + okcoinWsCandle900s
|
||||
okcoinWsIndexCandle1800s = okcoinWsIndexSubsection + okcoinWsCandle1800s
|
||||
okcoinWsIndexCandle3600s = okcoinWsIndexSubsection + okcoinWsCandle3600s
|
||||
okcoinWsIndexCandle7200s = okcoinWsIndexSubsection + okcoinWsCandle7200s
|
||||
okcoinWsIndexCandle14400s = okcoinWsIndexSubsection + okcoinWsCandle14400s
|
||||
okcoinWsIndexCandle21600s = okcoinWsIndexSubsection + okcoinWsCandle21600s
|
||||
okcoinWsIndexCandle43200s = okcoinWsIndexSubsection + okcoinWsCandle43200s
|
||||
okcoinWsIndexCandle86400s = okcoinWsIndexSubsection + okcoinWsCandle86400s
|
||||
okcoinWsIndexCandle604900s = okcoinWsIndexSubsection + okcoinWsCandle604900s
|
||||
// Futures endpoints
|
||||
okcoinWsFuturesTicker = okcoinWsFuturesSubsection + okcoinWsTicker
|
||||
okcoinWsFuturesCandle60s = okcoinWsFuturesSubsection + okcoinWsCandle60s
|
||||
okcoinWsFuturesCandle180s = okcoinWsFuturesSubsection + okcoinWsCandle180s
|
||||
okcoinWsFuturesCandle300s = okcoinWsFuturesSubsection + okcoinWsCandle300s
|
||||
okcoinWsFuturesCandle900s = okcoinWsFuturesSubsection + okcoinWsCandle900s
|
||||
okcoinWsFuturesCandle1800s = okcoinWsFuturesSubsection + okcoinWsCandle1800s
|
||||
okcoinWsFuturesCandle3600s = okcoinWsFuturesSubsection + okcoinWsCandle3600s
|
||||
okcoinWsFuturesCandle7200s = okcoinWsFuturesSubsection + okcoinWsCandle7200s
|
||||
okcoinWsFuturesCandle14400s = okcoinWsFuturesSubsection + okcoinWsCandle14400s
|
||||
okcoinWsFuturesCandle21600s = okcoinWsFuturesSubsection + okcoinWsCandle21600s
|
||||
okcoinWsFuturesCandle43200s = okcoinWsFuturesSubsection + okcoinWsCandle43200s
|
||||
okcoinWsFuturesCandle86400s = okcoinWsFuturesSubsection + okcoinWsCandle86400s
|
||||
okcoinWsFuturesCandle604900s = okcoinWsFuturesSubsection + okcoinWsCandle604900s
|
||||
okcoinWsFuturesTrade = okcoinWsFuturesSubsection + okcoinWsTrade
|
||||
okcoinWsFuturesEstimatedPrice = okcoinWsFuturesSubsection + okcoinWsTrade
|
||||
okcoinWsFuturesPriceRange = okcoinWsFuturesSubsection + okcoinWsPriceRange
|
||||
okcoinWsFuturesDepth = okcoinWsFuturesSubsection + okcoinWsDepth
|
||||
okcoinWsFuturesDepth5 = okcoinWsFuturesSubsection + okcoinWsDepth5
|
||||
okcoinWsFuturesMarkPrice = okcoinWsFuturesSubsection + okcoinWsMarkPrice
|
||||
okcoinWsFuturesAccount = okcoinWsFuturesSubsection + okcoinWsAccount
|
||||
okcoinWsFuturesPosition = okcoinWsFuturesSubsection + okcoinWsPosition
|
||||
okcoinWsFuturesOrder = okcoinWsFuturesSubsection + okcoinWsOrder
|
||||
|
||||
okcoinWsRateLimit = 30
|
||||
|
||||
allowableIterations = 25
|
||||
delimiterColon = ":"
|
||||
delimiterDash = "-"
|
||||
|
||||
maxConnByteLen = 4096
|
||||
)
|
||||
|
||||
// orderbookMutex Ensures if two entries arrive at once, only one can be
|
||||
// processed at a time
|
||||
var orderbookMutex sync.Mutex
|
||||
|
||||
var defaultSpotSubscribedChannels = []string{okcoinWsSpotDepth,
|
||||
okcoinWsSpotCandle300s,
|
||||
okcoinWsSpotTicker,
|
||||
okcoinWsSpotTrade}
|
||||
|
||||
var defaultFuturesSubscribedChannels = []string{okcoinWsFuturesDepth,
|
||||
okcoinWsFuturesCandle300s,
|
||||
okcoinWsFuturesTicker,
|
||||
okcoinWsFuturesTrade}
|
||||
|
||||
var defaultIndexSubscribedChannels = []string{okcoinWsIndexCandle300s,
|
||||
okcoinWsIndexTicker}
|
||||
|
||||
var defaultSwapSubscribedChannels = []string{okcoinWsSwapDepth,
|
||||
okcoinWsSwapCandle300s,
|
||||
okcoinWsSwapTicker,
|
||||
okcoinWsSwapTrade,
|
||||
okcoinWsSwapFundingRate,
|
||||
okcoinWsSwapMarkPrice}
|
||||
|
||||
// SetErrorDefaults sets the full error default list
|
||||
func (o *OKCoin) SetErrorDefaults() {
|
||||
o.ErrorCodes = map[string]error{
|
||||
"0": errors.New("successful"),
|
||||
"1": errors.New("invalid parameter in url normally"),
|
||||
"30001": errors.New("request header \"OK_ACCESS_KEY\" cannot be blank"),
|
||||
"30002": errors.New("request header \"OK_ACCESS_SIGN\" cannot be blank"),
|
||||
"30003": errors.New("request header \"OK_ACCESS_TIMESTAMP\" cannot be blank"),
|
||||
"30004": errors.New("request header \"OK_ACCESS_PASSPHRASE\" cannot be blank"),
|
||||
"30005": errors.New("invalid OK_ACCESS_TIMESTAMP"),
|
||||
"30006": errors.New("invalid OK_ACCESS_KEY"),
|
||||
"30007": errors.New("invalid Content_Type, please use \"application/json\" format"),
|
||||
"30008": errors.New("timestamp request expired"),
|
||||
"30009": errors.New("system error"),
|
||||
"30010": errors.New("api validation failed"),
|
||||
"30011": errors.New("invalid IP"),
|
||||
"30012": errors.New("invalid authorization"),
|
||||
"30013": errors.New("invalid sign"),
|
||||
"30014": errors.New("request too frequent"),
|
||||
"30015": errors.New("request header \"OK_ACCESS_PASSPHRASE\" incorrect"),
|
||||
"30016": errors.New("you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey"),
|
||||
"30017": errors.New("apikey's broker id does not match"),
|
||||
"30018": errors.New("apikey's domain does not match"),
|
||||
"30020": errors.New("body cannot be blank"),
|
||||
"30021": errors.New("json data format error"),
|
||||
"30023": errors.New("required parameter cannot be blank"),
|
||||
"30024": errors.New("parameter value error"),
|
||||
"30025": errors.New("parameter category error"),
|
||||
"30026": errors.New("requested too frequent; endpoint limit exceeded"),
|
||||
"30027": errors.New("login failure"),
|
||||
"30028": errors.New("unauthorized execution"),
|
||||
"30029": errors.New("account suspended"),
|
||||
"30030": errors.New("endpoint request failed. Please try again"),
|
||||
"30031": errors.New("token does not exist"),
|
||||
"30032": errors.New("pair does not exist"),
|
||||
"30033": errors.New("exchange domain does not exist"),
|
||||
"30034": errors.New("exchange ID does not exist"),
|
||||
"30035": errors.New("trading is not supported in this website"),
|
||||
"30036": errors.New("no relevant data"),
|
||||
"30037": errors.New("endpoint is offline or unavailable"),
|
||||
"30038": errors.New("user does not exist"),
|
||||
"32001": errors.New("futures account suspended"),
|
||||
"32002": errors.New("futures account does not exist"),
|
||||
"32003": errors.New("canceling, please wait"),
|
||||
"32004": errors.New("you have no unfilled orders"),
|
||||
"32005": errors.New("max order quantity"),
|
||||
"32006": errors.New("the order price or trigger price exceeds USD 1 million"),
|
||||
"32007": errors.New("leverage level must be the same for orders on the same side of the contract"),
|
||||
"32008": errors.New("max. positions to open (cross margin)"),
|
||||
"32009": errors.New("max. positions to open (fixed margin)"),
|
||||
"32010": errors.New("leverage cannot be changed with open positions"),
|
||||
"32011": errors.New("futures status error"),
|
||||
"32012": errors.New("futures order update error"),
|
||||
"32013": errors.New("token type is blank"),
|
||||
"32014": errors.New("your number of contracts closing is larger than the number of contracts available"),
|
||||
"32015": errors.New("margin ratio is lower than 100% before opening positions"),
|
||||
"32016": errors.New("margin ratio is lower than 100% after opening position"),
|
||||
"32017": errors.New("no BBO"),
|
||||
"32018": errors.New("the order quantity is less than 1, please try again"),
|
||||
"32019": errors.New("the order price deviates from the price of the previous minute by more than 3%"),
|
||||
"32020": errors.New("the price is not in the range of the price limit"),
|
||||
"32021": errors.New("leverage error"),
|
||||
"32022": errors.New("this function is not supported in your country or region according to the regulations"),
|
||||
"32023": errors.New("this account has outstanding loan"),
|
||||
"32024": errors.New("order cannot be placed during delivery"),
|
||||
"32025": errors.New("order cannot be placed during settlement"),
|
||||
"32026": errors.New("your account is restricted from opening positions"),
|
||||
"32027": errors.New("cancelled over 20 orders"),
|
||||
"32028": errors.New("account is suspended and liquidated"),
|
||||
"32029": errors.New("order info does not exist"),
|
||||
"33001": errors.New("margin account for this pair is not enabled yet"),
|
||||
"33002": errors.New("margin account for this pair is suspended"),
|
||||
"33003": errors.New("no loan balance"),
|
||||
"33004": errors.New("loan amount cannot be smaller than the minimum limit"),
|
||||
"33005": errors.New("repayment amount must exceed 0"),
|
||||
"33006": errors.New("loan order not found"),
|
||||
"33007": errors.New("status not found"),
|
||||
"33008": errors.New("loan amount cannot exceed the maximum limit"),
|
||||
"33009": errors.New("user ID is blank"),
|
||||
"33010": errors.New("you cannot cancel an order during session 2 of call auction"),
|
||||
"33011": errors.New("no new market data"),
|
||||
"33012": errors.New("order cancellation failed"),
|
||||
"33013": errors.New("order placement failed"),
|
||||
"33014": errors.New("order does not exist"),
|
||||
"33015": errors.New("exceeded maximum limit"),
|
||||
"33016": errors.New("margin trading is not open for this token"),
|
||||
"33017": errors.New("insufficient balance"),
|
||||
"33018": errors.New("this parameter must be smaller than 1"),
|
||||
"33020": errors.New("request not supported"),
|
||||
"33021": errors.New("token and the pair do not match"),
|
||||
"33022": errors.New("pair and the order do not match"),
|
||||
"33023": errors.New("you can only place market orders during call auction"),
|
||||
"33024": errors.New("trading amount too small"),
|
||||
"33025": errors.New("base token amount is blank"),
|
||||
"33026": errors.New("transaction completed"),
|
||||
"33027": errors.New("cancelled order or order cancelling"),
|
||||
"33028": errors.New("the decimal places of the trading price exceeded the limit"),
|
||||
"33029": errors.New("the decimal places of the trading size exceeded the limit"),
|
||||
"34001": errors.New("withdrawal suspended"),
|
||||
"34002": errors.New("please add a withdrawal address"),
|
||||
"34003": errors.New("sorry, this token cannot be withdrawn to xx at the moment"),
|
||||
"34004": errors.New("withdrawal fee is smaller than minimum limit"),
|
||||
"34005": errors.New("withdrawal fee exceeds the maximum limit"),
|
||||
"34006": errors.New("withdrawal amount is lower than the minimum limit"),
|
||||
"34007": errors.New("withdrawal amount exceeds the maximum limit"),
|
||||
"34008": errors.New("insufficient balance"),
|
||||
"34009": errors.New("your withdrawal amount exceeds the daily limit"),
|
||||
"34010": errors.New("transfer amount must be larger than 0"),
|
||||
"34011": errors.New("conditions not met"),
|
||||
"34012": errors.New("the minimum withdrawal amount for NEO is 1, and the amount must be an integer"),
|
||||
"34013": errors.New("please transfer"),
|
||||
"34014": errors.New("transfer limited"),
|
||||
"34015": errors.New("subaccount does not exist"),
|
||||
"34016": errors.New("transfer suspended"),
|
||||
"34017": errors.New("account suspended"),
|
||||
"34018": errors.New("incorrect trades password"),
|
||||
"34019": errors.New("please bind your email before withdrawal"),
|
||||
"34020": errors.New("please bind your funds password before withdrawal"),
|
||||
"34021": errors.New("not verified address"),
|
||||
"34022": errors.New("withdrawals are not available for sub accounts"),
|
||||
"35001": errors.New("contract subscribing does not exist"),
|
||||
"35002": errors.New("contract is being settled"),
|
||||
"35003": errors.New("contract is being paused"),
|
||||
"35004": errors.New("pending contract settlement"),
|
||||
"35005": errors.New("perpetual swap trading is not enabled"),
|
||||
"35008": errors.New("margin ratio too low when placing order"),
|
||||
"35010": errors.New("closing position size larger than available size"),
|
||||
"35012": errors.New("placing an order with less than 1 contract"),
|
||||
"35014": errors.New("order size is not in acceptable range"),
|
||||
"35015": errors.New("leverage level unavailable"),
|
||||
"35017": errors.New("changing leverage level"),
|
||||
"35019": errors.New("order size exceeds limit"),
|
||||
"35020": errors.New("order price exceeds limit"),
|
||||
"35021": errors.New("order size exceeds limit of the current tier"),
|
||||
"35022": errors.New("contract is paused or closed"),
|
||||
"35030": errors.New("place multiple orders"),
|
||||
"35031": errors.New("cancel multiple orders"),
|
||||
"35061": errors.New("invalid instrument_id"),
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package okgroup
|
||||
package okcoin
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -24,158 +23,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
// List of all websocket channels to subscribe to
|
||||
const (
|
||||
// Orderbook events
|
||||
okGroupWsOrderbookUpdate = "update"
|
||||
okGroupWsOrderbookPartial = "partial"
|
||||
// API subsections
|
||||
okGroupWsSwapSubsection = "swap/"
|
||||
okGroupWsIndexSubsection = "index/"
|
||||
okGroupWsFuturesSubsection = "futures/"
|
||||
okGroupWsSpotSubsection = "spot/"
|
||||
// Shared API endpoints
|
||||
okGroupWsCandle = "candle"
|
||||
okGroupWsCandle60s = okGroupWsCandle + "60s"
|
||||
okGroupWsCandle180s = okGroupWsCandle + "180s"
|
||||
okGroupWsCandle300s = okGroupWsCandle + "300s"
|
||||
okGroupWsCandle900s = okGroupWsCandle + "900s"
|
||||
okGroupWsCandle1800s = okGroupWsCandle + "1800s"
|
||||
okGroupWsCandle3600s = okGroupWsCandle + "3600s"
|
||||
okGroupWsCandle7200s = okGroupWsCandle + "7200s"
|
||||
okGroupWsCandle14400s = okGroupWsCandle + "14400s"
|
||||
okGroupWsCandle21600s = okGroupWsCandle + "21600"
|
||||
okGroupWsCandle43200s = okGroupWsCandle + "43200s"
|
||||
okGroupWsCandle86400s = okGroupWsCandle + "86400s"
|
||||
okGroupWsCandle604900s = okGroupWsCandle + "604800s"
|
||||
okGroupWsTicker = "ticker"
|
||||
okGroupWsTrade = "trade"
|
||||
okGroupWsDepth = "depth"
|
||||
okGroupWsDepth5 = "depth5"
|
||||
okGroupWsAccount = "account"
|
||||
okGroupWsMarginAccount = "margin_account"
|
||||
okGroupWsOrder = "order"
|
||||
okGroupWsFundingRate = "funding_rate"
|
||||
okGroupWsPriceRange = "price_range"
|
||||
okGroupWsMarkPrice = "mark_price"
|
||||
okGroupWsPosition = "position"
|
||||
okGroupWsEstimatedPrice = "estimated_price"
|
||||
// Spot endpoints
|
||||
okGroupWsSpotTicker = okGroupWsSpotSubsection + okGroupWsTicker
|
||||
okGroupWsSpotCandle60s = okGroupWsSpotSubsection + okGroupWsCandle60s
|
||||
okGroupWsSpotCandle180s = okGroupWsSpotSubsection + okGroupWsCandle180s
|
||||
okGroupWsSpotCandle300s = okGroupWsSpotSubsection + okGroupWsCandle300s
|
||||
okGroupWsSpotCandle900s = okGroupWsSpotSubsection + okGroupWsCandle900s
|
||||
okGroupWsSpotCandle1800s = okGroupWsSpotSubsection + okGroupWsCandle1800s
|
||||
okGroupWsSpotCandle3600s = okGroupWsSpotSubsection + okGroupWsCandle3600s
|
||||
okGroupWsSpotCandle7200s = okGroupWsSpotSubsection + okGroupWsCandle7200s
|
||||
okGroupWsSpotCandle14400s = okGroupWsSpotSubsection + okGroupWsCandle14400s
|
||||
okGroupWsSpotCandle21600s = okGroupWsSpotSubsection + okGroupWsCandle21600s
|
||||
okGroupWsSpotCandle43200s = okGroupWsSpotSubsection + okGroupWsCandle43200s
|
||||
okGroupWsSpotCandle86400s = okGroupWsSpotSubsection + okGroupWsCandle86400s
|
||||
okGroupWsSpotCandle604900s = okGroupWsSpotSubsection + okGroupWsCandle604900s
|
||||
okGroupWsSpotTrade = okGroupWsSpotSubsection + okGroupWsTrade
|
||||
okGroupWsSpotDepth = okGroupWsSpotSubsection + okGroupWsDepth
|
||||
okGroupWsSpotDepth5 = okGroupWsSpotSubsection + okGroupWsDepth5
|
||||
okGroupWsSpotAccount = okGroupWsSpotSubsection + okGroupWsAccount
|
||||
okGroupWsSpotMarginAccount = okGroupWsSpotSubsection + okGroupWsMarginAccount
|
||||
okGroupWsSpotOrder = okGroupWsSpotSubsection + okGroupWsOrder
|
||||
// Swap endpoints
|
||||
okGroupWsSwapTicker = okGroupWsSwapSubsection + okGroupWsTicker
|
||||
okGroupWsSwapCandle60s = okGroupWsSwapSubsection + okGroupWsCandle60s
|
||||
okGroupWsSwapCandle180s = okGroupWsSwapSubsection + okGroupWsCandle180s
|
||||
okGroupWsSwapCandle300s = okGroupWsSwapSubsection + okGroupWsCandle300s
|
||||
okGroupWsSwapCandle900s = okGroupWsSwapSubsection + okGroupWsCandle900s
|
||||
okGroupWsSwapCandle1800s = okGroupWsSwapSubsection + okGroupWsCandle1800s
|
||||
okGroupWsSwapCandle3600s = okGroupWsSwapSubsection + okGroupWsCandle3600s
|
||||
okGroupWsSwapCandle7200s = okGroupWsSwapSubsection + okGroupWsCandle7200s
|
||||
okGroupWsSwapCandle14400s = okGroupWsSwapSubsection + okGroupWsCandle14400s
|
||||
okGroupWsSwapCandle21600s = okGroupWsSwapSubsection + okGroupWsCandle21600s
|
||||
okGroupWsSwapCandle43200s = okGroupWsSwapSubsection + okGroupWsCandle43200s
|
||||
okGroupWsSwapCandle86400s = okGroupWsSwapSubsection + okGroupWsCandle86400s
|
||||
okGroupWsSwapCandle604900s = okGroupWsSwapSubsection + okGroupWsCandle604900s
|
||||
okGroupWsSwapTrade = okGroupWsSwapSubsection + okGroupWsTrade
|
||||
okGroupWsSwapDepth = okGroupWsSwapSubsection + okGroupWsDepth
|
||||
okGroupWsSwapDepth5 = okGroupWsSwapSubsection + okGroupWsDepth5
|
||||
okGroupWsSwapFundingRate = okGroupWsSwapSubsection + okGroupWsFundingRate
|
||||
okGroupWsSwapPriceRange = okGroupWsSwapSubsection + okGroupWsPriceRange
|
||||
okGroupWsSwapMarkPrice = okGroupWsSwapSubsection + okGroupWsMarkPrice
|
||||
okGroupWsSwapPosition = okGroupWsSwapSubsection + okGroupWsPosition
|
||||
okGroupWsSwapAccount = okGroupWsSwapSubsection + okGroupWsAccount
|
||||
okGroupWsSwapOrder = okGroupWsSwapSubsection + okGroupWsOrder
|
||||
// Index endpoints
|
||||
okGroupWsIndexTicker = okGroupWsIndexSubsection + okGroupWsTicker
|
||||
okGroupWsIndexCandle60s = okGroupWsIndexSubsection + okGroupWsCandle60s
|
||||
okGroupWsIndexCandle180s = okGroupWsIndexSubsection + okGroupWsCandle180s
|
||||
okGroupWsIndexCandle300s = okGroupWsIndexSubsection + okGroupWsCandle300s
|
||||
okGroupWsIndexCandle900s = okGroupWsIndexSubsection + okGroupWsCandle900s
|
||||
okGroupWsIndexCandle1800s = okGroupWsIndexSubsection + okGroupWsCandle1800s
|
||||
okGroupWsIndexCandle3600s = okGroupWsIndexSubsection + okGroupWsCandle3600s
|
||||
okGroupWsIndexCandle7200s = okGroupWsIndexSubsection + okGroupWsCandle7200s
|
||||
okGroupWsIndexCandle14400s = okGroupWsIndexSubsection + okGroupWsCandle14400s
|
||||
okGroupWsIndexCandle21600s = okGroupWsIndexSubsection + okGroupWsCandle21600s
|
||||
okGroupWsIndexCandle43200s = okGroupWsIndexSubsection + okGroupWsCandle43200s
|
||||
okGroupWsIndexCandle86400s = okGroupWsIndexSubsection + okGroupWsCandle86400s
|
||||
okGroupWsIndexCandle604900s = okGroupWsIndexSubsection + okGroupWsCandle604900s
|
||||
// Futures endpoints
|
||||
okGroupWsFuturesTicker = okGroupWsFuturesSubsection + okGroupWsTicker
|
||||
okGroupWsFuturesCandle60s = okGroupWsFuturesSubsection + okGroupWsCandle60s
|
||||
okGroupWsFuturesCandle180s = okGroupWsFuturesSubsection + okGroupWsCandle180s
|
||||
okGroupWsFuturesCandle300s = okGroupWsFuturesSubsection + okGroupWsCandle300s
|
||||
okGroupWsFuturesCandle900s = okGroupWsFuturesSubsection + okGroupWsCandle900s
|
||||
okGroupWsFuturesCandle1800s = okGroupWsFuturesSubsection + okGroupWsCandle1800s
|
||||
okGroupWsFuturesCandle3600s = okGroupWsFuturesSubsection + okGroupWsCandle3600s
|
||||
okGroupWsFuturesCandle7200s = okGroupWsFuturesSubsection + okGroupWsCandle7200s
|
||||
okGroupWsFuturesCandle14400s = okGroupWsFuturesSubsection + okGroupWsCandle14400s
|
||||
okGroupWsFuturesCandle21600s = okGroupWsFuturesSubsection + okGroupWsCandle21600s
|
||||
okGroupWsFuturesCandle43200s = okGroupWsFuturesSubsection + okGroupWsCandle43200s
|
||||
okGroupWsFuturesCandle86400s = okGroupWsFuturesSubsection + okGroupWsCandle86400s
|
||||
okGroupWsFuturesCandle604900s = okGroupWsFuturesSubsection + okGroupWsCandle604900s
|
||||
okGroupWsFuturesTrade = okGroupWsFuturesSubsection + okGroupWsTrade
|
||||
okGroupWsFuturesEstimatedPrice = okGroupWsFuturesSubsection + okGroupWsTrade
|
||||
okGroupWsFuturesPriceRange = okGroupWsFuturesSubsection + okGroupWsPriceRange
|
||||
okGroupWsFuturesDepth = okGroupWsFuturesSubsection + okGroupWsDepth
|
||||
okGroupWsFuturesDepth5 = okGroupWsFuturesSubsection + okGroupWsDepth5
|
||||
okGroupWsFuturesMarkPrice = okGroupWsFuturesSubsection + okGroupWsMarkPrice
|
||||
okGroupWsFuturesAccount = okGroupWsFuturesSubsection + okGroupWsAccount
|
||||
okGroupWsFuturesPosition = okGroupWsFuturesSubsection + okGroupWsPosition
|
||||
okGroupWsFuturesOrder = okGroupWsFuturesSubsection + okGroupWsOrder
|
||||
|
||||
okGroupWsRateLimit = 30
|
||||
|
||||
allowableIterations = 25
|
||||
delimiterColon = ":"
|
||||
delimiterDash = "-"
|
||||
|
||||
maxConnByteLen = 4096
|
||||
)
|
||||
|
||||
// orderbookMutex Ensures if two entries arrive at once, only one can be
|
||||
// processed at a time
|
||||
var orderbookMutex sync.Mutex
|
||||
|
||||
var defaultSpotSubscribedChannels = []string{okGroupWsSpotDepth,
|
||||
okGroupWsSpotCandle300s,
|
||||
okGroupWsSpotTicker,
|
||||
okGroupWsSpotTrade}
|
||||
|
||||
var defaultFuturesSubscribedChannels = []string{okGroupWsFuturesDepth,
|
||||
okGroupWsFuturesCandle300s,
|
||||
okGroupWsFuturesTicker,
|
||||
okGroupWsFuturesTrade}
|
||||
|
||||
var defaultIndexSubscribedChannels = []string{okGroupWsIndexCandle300s,
|
||||
okGroupWsIndexTicker}
|
||||
|
||||
var defaultSwapSubscribedChannels = []string{okGroupWsSwapDepth,
|
||||
okGroupWsSwapCandle300s,
|
||||
okGroupWsSwapTicker,
|
||||
okGroupWsSwapTrade,
|
||||
okGroupWsSwapFundingRate,
|
||||
okGroupWsSwapMarkPrice}
|
||||
|
||||
// WsConnect initiates a websocket connection
|
||||
func (o *OKGroup) WsConnect() error {
|
||||
func (o *OKCoin) WsConnect() error {
|
||||
if !o.Websocket.IsEnabled() || !o.IsEnabled() {
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
@@ -208,7 +57,7 @@ func (o *OKGroup) WsConnect() error {
|
||||
}
|
||||
|
||||
// WsLogin sends a login request to websocket to enable access to authenticated endpoints
|
||||
func (o *OKGroup) WsLogin(ctx context.Context) error {
|
||||
func (o *OKCoin) WsLogin(ctx context.Context) error {
|
||||
creds, err := o.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -242,7 +91,7 @@ func (o *OKGroup) WsLogin(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// WsReadData receives and passes on websocket messages for processing
|
||||
func (o *OKGroup) WsReadData() {
|
||||
func (o *OKCoin) WsReadData() {
|
||||
defer o.Websocket.Wg.Done()
|
||||
|
||||
for {
|
||||
@@ -258,7 +107,7 @@ func (o *OKGroup) WsReadData() {
|
||||
}
|
||||
|
||||
// WsHandleData will read websocket raw data and pass to appropriate handler
|
||||
func (o *OKGroup) WsHandleData(respRaw []byte) error {
|
||||
func (o *OKCoin) WsHandleData(respRaw []byte) error {
|
||||
var dataResponse WebsocketDataResponse
|
||||
err := json.Unmarshal(respRaw, &dataResponse)
|
||||
if err != nil {
|
||||
@@ -266,18 +115,18 @@ func (o *OKGroup) WsHandleData(respRaw []byte) error {
|
||||
}
|
||||
if len(dataResponse.Data) > 0 {
|
||||
switch o.GetWsChannelWithoutOrderType(dataResponse.Table) {
|
||||
case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s,
|
||||
okGroupWsCandle900s, okGroupWsCandle1800s, okGroupWsCandle3600s,
|
||||
okGroupWsCandle7200s, okGroupWsCandle14400s, okGroupWsCandle21600s,
|
||||
okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s:
|
||||
case okcoinWsCandle60s, okcoinWsCandle180s, okcoinWsCandle300s,
|
||||
okcoinWsCandle900s, okcoinWsCandle1800s, okcoinWsCandle3600s,
|
||||
okcoinWsCandle7200s, okcoinWsCandle14400s, okcoinWsCandle21600s,
|
||||
okcoinWsCandle43200s, okcoinWsCandle86400s, okcoinWsCandle604900s:
|
||||
return o.wsProcessCandles(respRaw)
|
||||
case okGroupWsDepth, okGroupWsDepth5:
|
||||
case okcoinWsDepth, okcoinWsDepth5:
|
||||
return o.WsProcessOrderBook(respRaw)
|
||||
case okGroupWsTicker:
|
||||
case okcoinWsTicker:
|
||||
return o.wsProcessTickers(respRaw)
|
||||
case okGroupWsTrade:
|
||||
case okcoinWsTrade:
|
||||
return o.wsProcessTrades(respRaw)
|
||||
case okGroupWsOrder:
|
||||
case okcoinWsOrder:
|
||||
return o.wsProcessOrder(respRaw)
|
||||
}
|
||||
o.Websocket.DataHandler <- stream.UnhandledMessageWarning{
|
||||
@@ -332,7 +181,7 @@ func StringToOrderStatus(num int64) (order.Status, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OKGroup) wsProcessOrder(respRaw []byte) error {
|
||||
func (o *OKCoin) wsProcessOrder(respRaw []byte) error {
|
||||
var resp WebsocketSpotOrderResponse
|
||||
err := json.Unmarshal(respRaw, &resp)
|
||||
if err != nil {
|
||||
@@ -398,7 +247,7 @@ func (o *OKGroup) wsProcessOrder(respRaw []byte) error {
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTickers(respRaw []byte) error {
|
||||
func (o *OKCoin) wsProcessTickers(respRaw []byte) error {
|
||||
var response WebsocketTickerData
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
@@ -408,15 +257,7 @@ func (o *OKGroup) wsProcessTickers(respRaw []byte) error {
|
||||
for i := range response.Data {
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1],
|
||||
f[2],
|
||||
currency.UnderscoreDelimiter)
|
||||
default:
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
c := currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
|
||||
baseVolume := response.Data[i].BaseVolume24h
|
||||
if response.Data[i].ContractVolume24h != 0 {
|
||||
@@ -439,7 +280,7 @@ func (o *OKGroup) wsProcessTickers(respRaw []byte) error {
|
||||
Bid: response.Data[i].BestBid,
|
||||
Ask: response.Data[i].BestAsk,
|
||||
Last: response.Data[i].Last,
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
AssetType: a,
|
||||
Pair: c,
|
||||
LastUpdated: response.Data[i].Timestamp,
|
||||
}
|
||||
@@ -448,7 +289,7 @@ func (o *OKGroup) wsProcessTickers(respRaw []byte) error {
|
||||
}
|
||||
|
||||
// wsProcessTrades converts trade data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTrades(respRaw []byte) error {
|
||||
func (o *OKCoin) wsProcessTrades(respRaw []byte) error {
|
||||
if !o.IsSaveTradeDataEnabled() {
|
||||
return nil
|
||||
}
|
||||
@@ -462,16 +303,7 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error {
|
||||
trades := make([]trade.Data, len(response.Data))
|
||||
for i := range response.Data {
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1],
|
||||
f[2],
|
||||
currency.UnderscoreDelimiter)
|
||||
default:
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
c := currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
|
||||
tSide, err := order.StringToOrderSide(response.Data[i].Side)
|
||||
if err != nil {
|
||||
@@ -487,7 +319,7 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error {
|
||||
}
|
||||
trades[i] = trade.Data{
|
||||
Amount: amount,
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
AssetType: a,
|
||||
CurrencyPair: c,
|
||||
Exchange: o.Name,
|
||||
Price: response.Data[i].Price,
|
||||
@@ -500,7 +332,7 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error {
|
||||
}
|
||||
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (o *OKGroup) wsProcessCandles(respRaw []byte) error {
|
||||
func (o *OKCoin) wsProcessCandles(respRaw []byte) error {
|
||||
var response WebsocketCandleResponse
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
@@ -510,16 +342,7 @@ func (o *OKGroup) wsProcessCandles(respRaw []byte) error {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
for i := range response.Data {
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1],
|
||||
f[2],
|
||||
currency.UnderscoreDelimiter)
|
||||
default:
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
c := currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
|
||||
timeData, err := time.Parse(time.RFC3339Nano,
|
||||
response.Data[i].Candle[0])
|
||||
@@ -529,11 +352,11 @@ func (o *OKGroup) wsProcessCandles(respRaw []byte) error {
|
||||
response.Data[i].Candle[0])
|
||||
}
|
||||
|
||||
candleIndex := strings.LastIndex(response.Table, okGroupWsCandle)
|
||||
candleInterval := response.Table[candleIndex+len(okGroupWsCandle):]
|
||||
candleIndex := strings.LastIndex(response.Table, okcoinWsCandle)
|
||||
candleInterval := response.Table[candleIndex+len(okcoinWsCandle):]
|
||||
|
||||
klineData := stream.KlineData{
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
AssetType: a,
|
||||
Pair: c,
|
||||
Exchange: o.Name,
|
||||
Timestamp: timeData,
|
||||
@@ -565,7 +388,7 @@ func (o *OKGroup) wsProcessCandles(respRaw []byte) error {
|
||||
}
|
||||
|
||||
// WsProcessOrderBook Validates the checksum and updates internal orderbook values
|
||||
func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error {
|
||||
func (o *OKCoin) WsProcessOrderBook(respRaw []byte) error {
|
||||
var response WebsocketOrderBooksData
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
@@ -576,18 +399,9 @@ func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
for i := range response.Data {
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c := currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1],
|
||||
f[2],
|
||||
currency.UnderscoreDelimiter)
|
||||
default:
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
if response.Action == okGroupWsOrderbookPartial {
|
||||
if response.Action == okcoinWsOrderbookPartial {
|
||||
err := o.WsProcessPartialOrderBook(&response.Data[i], c, a)
|
||||
if err != nil {
|
||||
err2 := o.wsResubscribeToOrderbook(&response)
|
||||
@@ -596,7 +410,7 @@ func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else if response.Action == okGroupWsOrderbookUpdate {
|
||||
} else if response.Action == okcoinWsOrderbookUpdate {
|
||||
if len(response.Data[i].Asks) == 0 && len(response.Data[i].Bids) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -613,18 +427,12 @@ func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OKGroup) wsResubscribeToOrderbook(response *WebsocketOrderBooksData) error {
|
||||
func (o *OKCoin) wsResubscribeToOrderbook(response *WebsocketOrderBooksData) error {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
for i := range response.Data {
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterDash)
|
||||
default:
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
c := currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
|
||||
channelToResubscribe := &stream.ChannelSubscription{
|
||||
Channel: response.Table,
|
||||
@@ -641,7 +449,7 @@ func (o *OKGroup) wsResubscribeToOrderbook(response *WebsocketOrderBooksData) er
|
||||
|
||||
// AppendWsOrderbookItems adds websocket orderbook data bid/asks into an
|
||||
// orderbook item array
|
||||
func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.Item, error) {
|
||||
func (o *OKCoin) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.Item, error) {
|
||||
items := make([]orderbook.Item, len(entries))
|
||||
for j := range entries {
|
||||
amount, err := strconv.ParseFloat(entries[j][1].(string), 64)
|
||||
@@ -659,7 +467,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 *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error {
|
||||
func (o *OKCoin) WsProcessPartialOrderBook(wsEventData *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error {
|
||||
signedChecksum, err := o.CalculatePartialOrderbookChecksum(wsEventData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s channel: %s. Orderbook unable to calculate partial orderbook checksum: %s",
|
||||
@@ -705,7 +513,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketOrderBook, ins
|
||||
// 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 *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error {
|
||||
func (o *OKCoin) WsProcessUpdateOrderbook(wsEventData *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error {
|
||||
update := orderbook.Update{
|
||||
Asset: a,
|
||||
Pair: instrument,
|
||||
@@ -748,7 +556,7 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketOrderBook, inst
|
||||
// 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 *WebsocketOrderBook) (int32, error) {
|
||||
func (o *OKCoin) CalculatePartialOrderbookChecksum(orderbookData *WebsocketOrderBook) (int32, error) {
|
||||
var checksum strings.Builder
|
||||
for i := 0; i < allowableIterations; i++ {
|
||||
if len(orderbookData.Bids)-1 >= i {
|
||||
@@ -789,7 +597,7 @@ func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketOrde
|
||||
// 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) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base) int32 {
|
||||
func (o *OKCoin) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base) int32 {
|
||||
var checksum strings.Builder
|
||||
for i := 0; i < allowableIterations; i++ {
|
||||
if len(orderbookData.Bids)-1 >= i {
|
||||
@@ -809,7 +617,7 @@ func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be
|
||||
// handled by ManageSubscriptions()
|
||||
func (o *OKGroup) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
func (o *OKCoin) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
assets := o.GetAssetTypes(true)
|
||||
for x := range assets {
|
||||
@@ -818,146 +626,46 @@ func (o *OKGroup) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch assets[x] {
|
||||
case asset.Spot:
|
||||
channels := defaultSpotSubscribedChannels
|
||||
if o.IsWebsocketAuthenticationSupported() {
|
||||
channels = append(channels,
|
||||
okGroupWsSpotMarginAccount,
|
||||
okGroupWsSpotAccount,
|
||||
okGroupWsSpotOrder)
|
||||
if assets[x] != asset.Spot {
|
||||
o.Websocket.DataHandler <- fmt.Errorf("%w %v", asset.ErrNotSupported, assets[x])
|
||||
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, assets[x])
|
||||
}
|
||||
channels := defaultSpotSubscribedChannels
|
||||
if o.IsWebsocketAuthenticationSupported() {
|
||||
channels = append(channels,
|
||||
okcoinWsSpotMarginAccount,
|
||||
okcoinWsSpotAccount,
|
||||
okcoinWsSpotOrder)
|
||||
}
|
||||
for i := range pairs {
|
||||
p, err := o.FormatExchangeCurrency(pairs[i], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range pairs {
|
||||
p, err := o.FormatExchangeCurrency(pairs[i], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for y := range channels {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: p,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
for y := range channels {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: p,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
case asset.Futures:
|
||||
channels := defaultFuturesSubscribedChannels
|
||||
if o.IsWebsocketAuthenticationSupported() {
|
||||
channels = append(channels,
|
||||
okGroupWsFuturesAccount,
|
||||
okGroupWsFuturesPosition,
|
||||
okGroupWsFuturesOrder)
|
||||
}
|
||||
var futuresAccountPairs currency.Pairs
|
||||
var futuresAccountCodes currency.Currencies
|
||||
|
||||
for i := range pairs {
|
||||
p, err := o.FormatExchangeCurrency(pairs[i], asset.Futures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for y := range channels {
|
||||
if channels[y] == okGroupWsFuturesAccount {
|
||||
currencyString := strings.Split(pairs[i].String(),
|
||||
currency.UnderscoreDelimiter)[0]
|
||||
newP, err := currency.NewPairFromString(currencyString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !futuresAccountCodes.Contains(newP.Base) {
|
||||
// subscribe to coin-margin futures trading mode
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: currency.NewPair(newP.Base, currency.EMPTYCODE),
|
||||
Asset: asset.Futures,
|
||||
})
|
||||
futuresAccountCodes = append(futuresAccountCodes, newP.Base)
|
||||
}
|
||||
|
||||
if newP.Quote != currency.USDT {
|
||||
// Only allows subscription to USDT margined pair
|
||||
continue
|
||||
}
|
||||
|
||||
if !futuresAccountPairs.Contains(newP, true) {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: newP,
|
||||
Asset: asset.Futures,
|
||||
})
|
||||
futuresAccountPairs = futuresAccountPairs.Add(newP)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: p,
|
||||
Asset: asset.Futures,
|
||||
})
|
||||
}
|
||||
}
|
||||
case asset.PerpetualSwap:
|
||||
channels := defaultSwapSubscribedChannels
|
||||
if o.IsWebsocketAuthenticationSupported() {
|
||||
channels = append(channels,
|
||||
okGroupWsSwapAccount,
|
||||
okGroupWsSwapPosition,
|
||||
okGroupWsSwapOrder)
|
||||
}
|
||||
for i := range pairs {
|
||||
p, err := o.FormatExchangeCurrency(pairs[i], asset.PerpetualSwap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for y := range channels {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[y],
|
||||
Currency: p,
|
||||
Asset: asset.PerpetualSwap,
|
||||
})
|
||||
}
|
||||
}
|
||||
case asset.Index:
|
||||
for i := range pairs {
|
||||
p, err := o.FormatExchangeCurrency(pairs[i], asset.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for y := range defaultIndexSubscribedChannels {
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: defaultIndexSubscribedChannels[y],
|
||||
Currency: p,
|
||||
Asset: asset.Index,
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
o.Websocket.DataHandler <- errors.New("unhandled asset type")
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (o *OKGroup) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
func (o *OKCoin) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
return o.handleSubscriptions("subscribe", channelsToSubscribe)
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (o *OKGroup) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
func (o *OKCoin) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
return o.handleSubscriptions("unsubscribe", channelsToUnsubscribe)
|
||||
}
|
||||
|
||||
func (o *OKGroup) handleSubscriptions(operation string, subs []stream.ChannelSubscription) error {
|
||||
func (o *OKCoin) handleSubscriptions(operation string, subs []stream.ChannelSubscription) error {
|
||||
request := WebsocketEventRequest{
|
||||
Operation: operation,
|
||||
}
|
||||
@@ -972,7 +680,7 @@ func (o *OKGroup) handleSubscriptions(operation string, subs []stream.ChannelSub
|
||||
copy(temp.Arguments, request.Arguments)
|
||||
|
||||
arg := subs[i].Channel + delimiterColon
|
||||
if strings.EqualFold(subs[i].Channel, okGroupWsSpotAccount) {
|
||||
if strings.EqualFold(subs[i].Channel, okcoinWsSpotAccount) {
|
||||
arg += subs[i].Currency.Base.String()
|
||||
} else {
|
||||
arg += subs[i].Currency.String()
|
||||
@@ -1026,7 +734,7 @@ func (o *OKGroup) handleSubscriptions(operation string, subs []stream.ChannelSub
|
||||
|
||||
// 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 {
|
||||
func (o *OKCoin) GetWsChannelWithoutOrderType(table string) string {
|
||||
index := strings.Index(table, "/")
|
||||
if index == -1 {
|
||||
return table
|
||||
@@ -1043,17 +751,11 @@ func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string {
|
||||
|
||||
// GetAssetTypeFromTableName gets the asset type from the table name
|
||||
// eg "spot/ticker:BTCUSD" results in "SPOT"
|
||||
func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item {
|
||||
func (o *OKCoin) 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,
|
||||
@@ -4,22 +4,28 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
// GetDefaultConfig returns a default exchange config
|
||||
@@ -48,7 +54,6 @@ func (o *OKCoin) GetDefaultConfig() (*config.Exchange, error) {
|
||||
// SetDefaults method assignes the default values for OKCoin
|
||||
func (o *OKCoin) SetDefaults() {
|
||||
o.SetErrorDefaults()
|
||||
o.SetCheckVarDefaults()
|
||||
o.Name = okCoinExchangeName
|
||||
o.Enabled = true
|
||||
o.Verbose = true
|
||||
@@ -149,14 +154,53 @@ func (o *OKCoin) SetDefaults() {
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
o.APIVersion = okCoinAPIVersion
|
||||
o.Websocket = stream.New()
|
||||
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Start starts the OKGroup go routine
|
||||
// Setup sets user exchange configuration settings
|
||||
func (o *OKCoin) Setup(exch *config.Exchange) error {
|
||||
err := exch.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exch.Enabled {
|
||||
o.SetEnabled(false)
|
||||
return nil
|
||||
}
|
||||
err = o.SetupDefaults(exch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wsEndpoint, err := o.API.Endpoints.GetURL(exchange.WebsocketSpot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = o.Websocket.Setup(&stream.WebsocketSetup{
|
||||
ExchangeConfig: exch,
|
||||
DefaultURL: wsEndpoint,
|
||||
RunningURL: wsEndpoint,
|
||||
Connector: o.WsConnect,
|
||||
Subscriber: o.Subscribe,
|
||||
Unsubscriber: o.Unsubscribe,
|
||||
GenerateSubscriptions: o.GenerateDefaultSubscriptions,
|
||||
Features: &o.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return o.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
RateLimit: okcoinWsRateLimit,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts the OKCoin go routine
|
||||
func (o *OKCoin) Start(wg *sync.WaitGroup) error {
|
||||
if wg == nil {
|
||||
return fmt.Errorf("%T %w", wg, common.ErrNilPointer)
|
||||
@@ -173,15 +217,17 @@ func (o *OKCoin) Start(wg *sync.WaitGroup) error {
|
||||
func (o *OKCoin) Run() {
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Websocket: %s. (url: %s).\n",
|
||||
"%s Websocket: %s.",
|
||||
o.Name,
|
||||
common.IsEnabled(o.Websocket.IsEnabled()),
|
||||
o.WebsocketURL)
|
||||
common.IsEnabled(o.Websocket.IsEnabled()))
|
||||
o.PrintEnabledPairs()
|
||||
}
|
||||
|
||||
forceUpdate := false
|
||||
var err error
|
||||
if !o.BypassConfigFormatUpgrades {
|
||||
format, err := o.GetPairFormat(asset.Spot, false)
|
||||
var format currency.PairFormat
|
||||
format, err = o.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
@@ -189,7 +235,8 @@ func (o *OKCoin) Run() {
|
||||
err)
|
||||
return
|
||||
}
|
||||
enabled, err := o.CurrencyPairs.GetPairs(asset.Spot, true)
|
||||
var enabled, avail currency.Pairs
|
||||
enabled, err = o.CurrencyPairs.GetPairs(asset.Spot, true)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
@@ -198,7 +245,7 @@ func (o *OKCoin) Run() {
|
||||
return
|
||||
}
|
||||
|
||||
avail, err := o.CurrencyPairs.GetPairs(asset.Spot, false)
|
||||
avail, err = o.CurrencyPairs.GetPairs(asset.Spot, false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
@@ -237,7 +284,7 @@ func (o *OKCoin) Run() {
|
||||
return
|
||||
}
|
||||
|
||||
err := o.UpdateTradablePairs(context.TODO(), forceUpdate)
|
||||
err = o.UpdateTradablePairs(context.TODO(), forceUpdate)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update tradable pairs. Err: %s",
|
||||
@@ -247,7 +294,7 @@ func (o *OKCoin) Run() {
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (o *OKCoin) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) {
|
||||
func (o *OKCoin) FetchTradablePairs(ctx context.Context, _ asset.Item) (currency.Pairs, error) {
|
||||
prods, err := o.GetSpotTokenPairDetails(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -323,12 +370,12 @@ func (o *OKCoin) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item
|
||||
}
|
||||
|
||||
// FetchTicker returns the ticker for a currency pair
|
||||
func (o *OKCoin) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (tickerData *ticker.Price, err error) {
|
||||
tickerData, err = ticker.GetTicker(o.Name, p, assetType)
|
||||
func (o *OKCoin) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerData, err := ticker.GetTicker(o.Name, p, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateTicker(ctx, p, assetType)
|
||||
}
|
||||
return
|
||||
return tickerData, nil
|
||||
}
|
||||
|
||||
// GetRecentTrades returns the most recent trades for a currency and asset
|
||||
@@ -341,9 +388,9 @@ func (o *OKCoin) GetRecentTrades(ctx context.Context, p currency.Pair, assetType
|
||||
var resp []trade.Data
|
||||
switch assetType {
|
||||
case asset.Spot:
|
||||
var tradeData []okgroup.GetSpotFilledOrdersInformationResponse
|
||||
var tradeData []GetSpotFilledOrdersInformationResponse
|
||||
tradeData, err = o.GetSpotFilledOrdersInformation(ctx,
|
||||
okgroup.GetSpotFilledOrdersInformationRequest{
|
||||
&GetSpotFilledOrdersInformationRequest{
|
||||
InstrumentID: p.String(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -379,6 +426,680 @@ func (o *OKCoin) GetRecentTrades(ctx context.Context, p currency.Pair, assetType
|
||||
}
|
||||
|
||||
// CancelBatchOrders cancels an orders by their corresponding ID numbers
|
||||
func (o *OKCoin) CancelBatchOrders(ctx context.Context, orders []order.Cancel) (order.CancelBatchResponse, error) {
|
||||
func (o *OKCoin) CancelBatchOrders(_ context.Context, _ []order.Cancel) (order.CancelBatchResponse, error) {
|
||||
return order.CancelBatchResponse{}, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// FetchOrderbook returns orderbook base on the currency pair
|
||||
func (o *OKCoin) FetchOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
fPair, err := o.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ob, err := orderbook.Get(o.Name, fPair, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateOrderbook(ctx, fPair, assetType)
|
||||
}
|
||||
return ob, nil
|
||||
}
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (o *OKCoin) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.Item) (*orderbook.Base, error) {
|
||||
book := &orderbook.Base{
|
||||
Exchange: o.Name,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
VerifyOrderbook: o.CanVerifyOrderbook,
|
||||
}
|
||||
|
||||
fPair, err := o.FormatExchangeCurrency(p, a)
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
orderbookNew, err := o.GetOrderBook(ctx,
|
||||
&GetOrderBookRequest{
|
||||
InstrumentID: fPair.String(),
|
||||
Size: 200,
|
||||
}, a)
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
book.Bids = make(orderbook.Items, len(orderbookNew.Bids))
|
||||
for x := range orderbookNew.Bids {
|
||||
amount, convErr := strconv.ParseFloat(orderbookNew.Bids[x][1], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
price, convErr := strconv.ParseFloat(orderbookNew.Bids[x][0], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
var liquidationOrders, orderCount int64
|
||||
// Contract specific variables
|
||||
if len(orderbookNew.Bids[x]) == 4 {
|
||||
liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Bids[x][2], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
orderCount, convErr = strconv.ParseInt(orderbookNew.Bids[x][3], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
}
|
||||
|
||||
book.Bids[x] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
LiquidationOrders: liquidationOrders,
|
||||
OrderCount: orderCount,
|
||||
}
|
||||
}
|
||||
|
||||
book.Asks = make(orderbook.Items, len(orderbookNew.Asks))
|
||||
for x := range orderbookNew.Asks {
|
||||
amount, convErr := strconv.ParseFloat(orderbookNew.Asks[x][1], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
price, convErr := strconv.ParseFloat(orderbookNew.Asks[x][0], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
var liquidationOrders, orderCount int64
|
||||
// Contract specific variables
|
||||
if len(orderbookNew.Asks[x]) == 4 {
|
||||
liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Asks[x][2], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
orderCount, convErr = strconv.ParseInt(orderbookNew.Asks[x][3], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
}
|
||||
|
||||
book.Asks[x] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
LiquidationOrders: liquidationOrders,
|
||||
OrderCount: orderCount,
|
||||
}
|
||||
}
|
||||
|
||||
err = book.Process()
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
return orderbook.Get(o.Name, fPair, a)
|
||||
}
|
||||
|
||||
// UpdateAccountInfo retrieves balances for all enabled currencies
|
||||
func (o *OKCoin) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
|
||||
currencies, err := o.GetSpotTradingAccounts(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
|
||||
var resp account.Holdings
|
||||
resp.Exchange = o.Name
|
||||
currencyAccount := account.SubAccount{AssetType: assetType}
|
||||
|
||||
for i := range currencies {
|
||||
hold, parseErr := strconv.ParseFloat(currencies[i].Hold, 64)
|
||||
if parseErr != nil {
|
||||
return resp, parseErr
|
||||
}
|
||||
totalValue, parseErr := strconv.ParseFloat(currencies[i].Balance, 64)
|
||||
if parseErr != nil {
|
||||
return resp, parseErr
|
||||
}
|
||||
currencyAccount.Currencies = append(currencyAccount.Currencies,
|
||||
account.Balance{
|
||||
CurrencyName: currency.NewCode(currencies[i].Currency),
|
||||
Total: totalValue,
|
||||
Hold: hold,
|
||||
Free: totalValue - hold,
|
||||
})
|
||||
}
|
||||
|
||||
resp.Accounts = append(resp.Accounts, currencyAccount)
|
||||
|
||||
creds, err := o.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
err = account.Process(&resp, creds)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// FetchAccountInfo retrieves balances for all enabled currencies
|
||||
func (o *OKCoin) FetchAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
|
||||
creds, err := o.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
acc, err := account.GetHoldings(o.Name, creds, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateAccountInfo(ctx, assetType)
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// GetFundingHistory returns funding history, deposits and
|
||||
// withdrawals
|
||||
func (o *OKCoin) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, error) {
|
||||
accountDepositHistory, err := o.GetAccountDepositHistory(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accountWithdrawlHistory, err := o.GetAccountWithdrawalHistory(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]exchange.FundHistory, len(accountDepositHistory)+len(accountWithdrawlHistory))
|
||||
for x := range accountDepositHistory {
|
||||
orderStatus := ""
|
||||
switch accountDepositHistory[x].Status {
|
||||
case 0:
|
||||
orderStatus = "waiting"
|
||||
case 1:
|
||||
orderStatus = "confirmation account"
|
||||
case 2:
|
||||
orderStatus = "recharge success"
|
||||
}
|
||||
|
||||
resp[x] = exchange.FundHistory{
|
||||
Amount: accountDepositHistory[x].Amount,
|
||||
Currency: accountDepositHistory[x].Currency,
|
||||
ExchangeName: o.Name,
|
||||
Status: orderStatus,
|
||||
Timestamp: accountDepositHistory[x].Timestamp,
|
||||
TransferID: accountDepositHistory[x].TransactionID,
|
||||
TransferType: "deposit",
|
||||
}
|
||||
}
|
||||
|
||||
for i := range accountWithdrawlHistory {
|
||||
resp[len(accountDepositHistory)+i] = exchange.FundHistory{
|
||||
Amount: accountWithdrawlHistory[i].Amount,
|
||||
Currency: accountWithdrawlHistory[i].Currency,
|
||||
ExchangeName: o.Name,
|
||||
Status: OrderStatus[accountWithdrawlHistory[i].Status],
|
||||
Timestamp: accountWithdrawlHistory[i].Timestamp,
|
||||
TransferID: accountWithdrawlHistory[i].TransactionID,
|
||||
TransferType: "withdrawal",
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
func (o *OKCoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) {
|
||||
err := s.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fPair, err := o.FormatExchangeCurrency(s.Pair, s.AssetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := PlaceOrderRequest{
|
||||
ClientOID: s.ClientID,
|
||||
InstrumentID: fPair.String(),
|
||||
Side: s.Side.Lower(),
|
||||
Type: s.Type.Lower(),
|
||||
Size: strconv.FormatFloat(s.Amount, 'f', -1, 64),
|
||||
}
|
||||
if s.Type == order.Limit {
|
||||
req.Price = strconv.FormatFloat(s.Price, 'f', -1, 64)
|
||||
}
|
||||
|
||||
orderResponse, err := o.PlaceSpotOrder(ctx, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !orderResponse.Result {
|
||||
return nil, order.ErrUnableToPlaceOrder
|
||||
}
|
||||
return s.DeriveSubmitResponse(orderResponse.OrderID)
|
||||
}
|
||||
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (o *OKCoin) ModifyOrder(_ context.Context, _ *order.Modify) (*order.ModifyResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (o *OKCoin) CancelOrder(ctx context.Context, cancel *order.Cancel) error {
|
||||
err := cancel.Validate(cancel.StandardCancel())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orderID, err := strconv.ParseInt(cancel.OrderID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fpair, err := o.FormatExchangeCurrency(cancel.Pair,
|
||||
cancel.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orderCancellationResponse, err := o.CancelSpotOrder(ctx,
|
||||
&CancelSpotOrderRequest{
|
||||
InstrumentID: fpair.String(),
|
||||
OrderID: orderID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !orderCancellationResponse.Result {
|
||||
return fmt.Errorf("order %d failed to be cancelled",
|
||||
orderCancellationResponse.OrderID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
func (o *OKCoin) CancelAllOrders(ctx context.Context, orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
|
||||
if err := orderCancellation.Validate(); err != nil {
|
||||
return order.CancelAllResponse{}, err
|
||||
}
|
||||
|
||||
orderIDs := strings.Split(orderCancellation.OrderID, ",")
|
||||
resp := order.CancelAllResponse{}
|
||||
resp.Status = make(map[string]string)
|
||||
orderIDNumbers := make([]int64, 0, len(orderIDs))
|
||||
for i := range orderIDs {
|
||||
orderIDNumber, err := strconv.ParseInt(orderIDs[i], 10, 64)
|
||||
if err != nil {
|
||||
resp.Status[orderIDs[i]] = err.Error()
|
||||
continue
|
||||
}
|
||||
orderIDNumbers = append(orderIDNumbers, orderIDNumber)
|
||||
}
|
||||
|
||||
fpair, err := o.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
orderCancellation.AssetType)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
cancelOrdersResponse, err := o.CancelMultipleSpotOrders(ctx,
|
||||
&CancelMultipleSpotOrdersRequest{
|
||||
InstrumentID: fpair.String(),
|
||||
OrderIDs: orderIDNumbers,
|
||||
})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
for x := range cancelOrdersResponse {
|
||||
for y := range cancelOrdersResponse[x] {
|
||||
resp.Status[strconv.FormatInt(cancelOrdersResponse[x][y].OrderID, 10)] = strconv.FormatBool(cancelOrdersResponse[x][y].Result)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetOrderInfo returns order information based on order ID
|
||||
func (o *OKCoin) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (order.Detail, error) {
|
||||
var resp order.Detail
|
||||
if assetType != asset.Spot {
|
||||
return resp, fmt.Errorf("%s %w", assetType, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
mOrder, err := o.GetSpotOrder(ctx, &GetSpotOrderRequest{OrderID: orderID})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
format, err := o.GetPairFormat(assetType, false)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(mOrder.InstrumentID, format.Delimiter)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
status, err := order.StringToOrderStatus(mOrder.Status)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
|
||||
side, err := order.StringToOrderSide(mOrder.Side)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
resp = order.Detail{
|
||||
Amount: mOrder.Size,
|
||||
Pair: p,
|
||||
Exchange: o.Name,
|
||||
Date: mOrder.Timestamp,
|
||||
ExecutedAmount: mOrder.FilledSize,
|
||||
Status: status,
|
||||
Side: side,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (o *OKCoin) GetDepositAddress(ctx context.Context, c currency.Code, _, _ string) (*deposit.Address, error) {
|
||||
wallet, err := o.GetAccountDepositAddressForCurrency(ctx, c.Lower().String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(wallet) == 0 {
|
||||
return nil, fmt.Errorf("%w for currency %s",
|
||||
errNoAccountDepositAddress,
|
||||
c)
|
||||
}
|
||||
return &deposit.Address{
|
||||
Address: wallet[0].Address,
|
||||
Tag: wallet[0].Tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
// submitted
|
||||
func (o *OKCoin) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
if err := withdrawRequest.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
withdrawal, err := o.AccountWithdraw(ctx,
|
||||
&AccountWithdrawRequest{
|
||||
Amount: withdrawRequest.Amount,
|
||||
Currency: withdrawRequest.Currency.Lower().String(),
|
||||
Destination: 4, // 1, 2, 3 are all internal
|
||||
Fee: withdrawRequest.Crypto.FeeAmount,
|
||||
ToAddress: withdrawRequest.Crypto.Address,
|
||||
TradePwd: withdrawRequest.TradePassword,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !withdrawal.Result {
|
||||
return nil,
|
||||
fmt.Errorf("could not withdraw currency %s to %s, no error specified",
|
||||
withdrawRequest.Currency,
|
||||
withdrawRequest.Crypto.Address)
|
||||
}
|
||||
|
||||
return &withdraw.ExchangeResponse{
|
||||
ID: strconv.FormatInt(withdrawal.WithdrawalID, 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawFiatFunds returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKCoin) WithdrawFiatFunds(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKCoin) WithdrawFiatFundsToInternationalBank(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWithdrawalsHistory returns previous withdrawals data
|
||||
func (o *OKCoin) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) ([]exchange.WithdrawalHistory, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (o *OKCoin) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []order.Detail
|
||||
for x := range req.Pairs {
|
||||
var fPair currency.Pair
|
||||
fPair, err = o.FormatExchangeCurrency(req.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var spotOpenOrders []GetSpotOrderResponse
|
||||
spotOpenOrders, err = o.GetSpotOpenOrders(ctx,
|
||||
&GetSpotOpenOrdersRequest{
|
||||
InstrumentID: fPair.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range spotOpenOrders {
|
||||
var status order.Status
|
||||
status, err = order.StringToOrderStatus(spotOpenOrders[i].Status)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var side order.Side
|
||||
side, err = order.StringToOrderSide(spotOpenOrders[i].Side)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var orderType order.Type
|
||||
orderType, err = order.StringToOrderType(spotOpenOrders[i].Type)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
resp = append(resp, order.Detail{
|
||||
OrderID: spotOpenOrders[i].OrderID,
|
||||
Price: spotOpenOrders[i].Price,
|
||||
Amount: spotOpenOrders[i].Size,
|
||||
Pair: req.Pairs[x],
|
||||
Exchange: o.Name,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
ExecutedAmount: spotOpenOrders[i].FilledSize,
|
||||
Date: spotOpenOrders[i].Timestamp,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
return req.Filter(o.Name, resp), nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (o *OKCoin) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []order.Detail
|
||||
for x := range req.Pairs {
|
||||
var fPair currency.Pair
|
||||
fPair, err = o.FormatExchangeCurrency(req.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var spotOrders []GetSpotOrderResponse
|
||||
spotOrders, err = o.GetSpotOrders(ctx,
|
||||
&GetSpotOrdersRequest{
|
||||
Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"),
|
||||
InstrumentID: fPair.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range spotOrders {
|
||||
var status order.Status
|
||||
status, err = order.StringToOrderStatus(spotOrders[i].Status)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var side order.Side
|
||||
side, err = order.StringToOrderSide(spotOrders[i].Side)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var orderType order.Type
|
||||
orderType, err = order.StringToOrderType(spotOrders[i].Type)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
detail := order.Detail{
|
||||
OrderID: spotOrders[i].OrderID,
|
||||
Price: spotOrders[i].Price,
|
||||
AverageExecutedPrice: spotOrders[i].PriceAvg,
|
||||
Amount: spotOrders[i].Size,
|
||||
ExecutedAmount: spotOrders[i].FilledSize,
|
||||
RemainingAmount: spotOrders[i].Size - spotOrders[i].FilledSize,
|
||||
Pair: req.Pairs[x],
|
||||
Exchange: o.Name,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
Date: spotOrders[i].Timestamp,
|
||||
Status: status,
|
||||
}
|
||||
detail.InferCostsAndTimes()
|
||||
resp = append(resp, detail)
|
||||
}
|
||||
}
|
||||
return req.Filter(o.Name, resp), nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (o *OKCoin) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if feeBuilder == nil {
|
||||
return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer)
|
||||
}
|
||||
if !o.AreCredentialsValid(ctx) && // Todo check connection status
|
||||
feeBuilder.FeeType == exchange.CryptocurrencyTradeFee {
|
||||
feeBuilder.FeeType = exchange.OfflineTradeFee
|
||||
}
|
||||
return o.GetFee(ctx, feeBuilder)
|
||||
}
|
||||
|
||||
// GetWithdrawCapabilities returns the types of withdrawal methods permitted by the exchange
|
||||
func (o *OKCoin) GetWithdrawCapabilities() uint32 {
|
||||
return o.GetWithdrawPermissions()
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (o *OKCoin) AuthenticateWebsocket(ctx context.Context) error {
|
||||
return o.WsLogin(ctx)
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (o *OKCoin) ValidateCredentials(ctx context.Context, assetType asset.Item) error {
|
||||
_, err := o.UpdateAccountInfo(ctx, assetType)
|
||||
return o.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func (o *OKCoin) GetHistoricTrades(_ context.Context, _ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (o *OKCoin) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if err := o.ValidateKline(pair, a, interval); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
formattedPair, err := o.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
req := &GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: start.UTC().Format(time.RFC3339),
|
||||
End: end.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: formattedPair.String(),
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
ret.Candles, err = o.GetMarketData(ctx, req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (o *OKCoin) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if err := o.ValidateKline(pair, a, interval); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
formattedPair, err := o.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for x := range dates.Ranges {
|
||||
req := &GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: dates.Ranges[x].Start.Time.UTC().Format(time.RFC3339),
|
||||
End: dates.Ranges[x].End.Time.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: formattedPair.String(),
|
||||
}
|
||||
|
||||
var candles []kline.Candle
|
||||
candles, err = o.GetMarketData(ctx, req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, candles...)
|
||||
}
|
||||
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", o.Base.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
# GoCryptoTrader package Okgroup
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/okgroup)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This OKCoin package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://gocryptotrader.herokuapp.com/)
|
||||
|
||||
## OKCoin Exchange
|
||||
|
||||
### Current Features
|
||||
|
||||
+ REST Support
|
||||
|
||||
### How to enable
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### How to do REST public/private calls
|
||||
|
||||
+ If enabled via "configuration".json file the exchange will be added to the
|
||||
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
|
||||
the wrapper interface functions for accessing exchange data. View routines.go
|
||||
for an example of integration usage with GoCryptoTrader. Rudimentary example
|
||||
below:
|
||||
|
||||
main.go
|
||||
```go
|
||||
var o exchange.IBotExchange
|
||||
|
||||
for i := range Bot.Exchanges {
|
||||
if Bot.Exchanges[i].GetName() == "OKCoin" {
|
||||
o = Bot.Exchanges[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Public calls - wrapper functions
|
||||
|
||||
// Fetches current ticker information
|
||||
tick, err := o.FetchTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := o.FetchOrderbook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
|
||||
// set and AuthenticatedAPISupport is set to true
|
||||
|
||||
// Fetches current account information
|
||||
accountInfo, err := o.GetAccountInfo()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
+ If enabled via individually importing package, rudimentary example below:
|
||||
|
||||
```go
|
||||
// Public calls
|
||||
|
||||
// Fetches current ticker information
|
||||
ticker, err := o.GetSpotTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := o.GetSpotMarketDepth()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - make sure your APIKEY and APISECRET are set and
|
||||
// AuthenticatedAPISupport is set to true
|
||||
|
||||
// GetContractPosition returns contract positioning
|
||||
accountInfo, err := o.GetContractPosition(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Submits an order and the exchange and returns its tradeID
|
||||
tradeID, err := o.PlaceContractOrders(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
|
||||
@@ -1,842 +0,0 @@
|
||||
package okgroup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// OKGroupAPIPath const to help with api url formatting
|
||||
OKGroupAPIPath = "api/"
|
||||
// API subsections
|
||||
okGroupAccountSubsection = "account"
|
||||
okGroupTokenSubsection = "spot"
|
||||
okGroupMarginTradingSubsection = "margin"
|
||||
okGroupFuturesTradingSubSection = "futures"
|
||||
oKGroupSwapTradingSubSection = "swap"
|
||||
// OKGroupAccounts common api endpoint
|
||||
OKGroupAccounts = "accounts"
|
||||
// OKGroupLedger common api endpoint
|
||||
OKGroupLedger = "ledger"
|
||||
// OKGroupOrders common api endpoint
|
||||
OKGroupOrders = "orders"
|
||||
// OKGroupBatchOrders common api endpoint
|
||||
OKGroupBatchOrders = "batch_orders"
|
||||
// OKGroupCancelOrders common api endpoint
|
||||
OKGroupCancelOrders = "cancel_orders"
|
||||
// OKGroupCancelOrder common api endpoint
|
||||
OKGroupCancelOrder = "cancel_order"
|
||||
// OKGroupCancelBatchOrders common api endpoint
|
||||
OKGroupCancelBatchOrders = "cancel_batch_orders"
|
||||
// OKGroupPendingOrders common api endpoint
|
||||
OKGroupPendingOrders = "orders_pending"
|
||||
// OKGroupTrades common api endpoint
|
||||
OKGroupTrades = "trades"
|
||||
// OKGroupTicker common api endpoint
|
||||
OKGroupTicker = "ticker"
|
||||
// OKGroupInstruments common api endpoint
|
||||
OKGroupInstruments = "instruments"
|
||||
// OKGroupLiquidation common api endpoint
|
||||
OKGroupLiquidation = "liquidation"
|
||||
// OKGroupMarkPrice common api endpoint
|
||||
OKGroupMarkPrice = "mark_price"
|
||||
// OKGroupGetAccountDepositHistory common api endpoint
|
||||
OKGroupGetAccountDepositHistory = "deposit/history"
|
||||
// OKGroupGetSpotTransactionDetails common api endpoint
|
||||
OKGroupGetSpotTransactionDetails = "fills"
|
||||
// OKGroupGetSpotOrderBook common api endpoint
|
||||
OKGroupGetSpotOrderBook = "book"
|
||||
// OKGroupGetSpotMarketData common api endpoint
|
||||
OKGroupGetSpotMarketData = "candles"
|
||||
// OKGroupPriceLimit common api endpoint
|
||||
OKGroupPriceLimit = "price_limit"
|
||||
// Account based endpoints
|
||||
okGroupGetAccountCurrencies = "currencies"
|
||||
okGroupGetAccountWalletInformation = "wallet"
|
||||
okGroupFundsTransfer = "transfer"
|
||||
okGroupWithdraw = "withdrawal"
|
||||
okGroupGetWithdrawalFees = "withdrawal/fee"
|
||||
okGroupGetWithdrawalHistory = "withdrawal/history"
|
||||
okGroupGetDepositAddress = "deposit/address"
|
||||
// Margin based endpoints
|
||||
okGroupGetMarketAvailability = "availability"
|
||||
okGroupGetLoanHistory = "borrowed"
|
||||
okGroupGetLoan = "borrow"
|
||||
okGroupGetRepayment = "repayment"
|
||||
)
|
||||
|
||||
// OKGroup is the overaching type across the all of OKCoin's exchange methods
|
||||
type OKGroup struct {
|
||||
exchange.Base
|
||||
ExchangeName string
|
||||
// Spot and contract market error codes
|
||||
ErrorCodes map[string]error
|
||||
// Stores for corresponding variable checks
|
||||
ContractTypes []string
|
||||
CurrencyPairsDefaults []string
|
||||
ContractPosition []string
|
||||
Types []string
|
||||
// URLs to be overridden by implementations of OKGroup
|
||||
APIURL string
|
||||
APIVersion string
|
||||
WebsocketURL string
|
||||
}
|
||||
|
||||
// GetAccountCurrencies returns a list of tradable spot instruments and their properties
|
||||
func (o *OKGroup) GetAccountCurrencies(ctx context.Context) (resp []GetAccountCurrenciesResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWalletInformation returns a list of wallets and their properties
|
||||
func (o *OKGroup) GetAccountWalletInformation(ctx context.Context, currency string) (resp []WalletInformationResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v", okGroupGetAccountWalletInformation, currency)
|
||||
} else {
|
||||
requestURL = okGroupGetAccountWalletInformation
|
||||
}
|
||||
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// TransferAccountFunds the transfer of funds between wallet, trading accounts, main account and sub accounts.
|
||||
func (o *OKGroup) TransferAccountFunds(ctx context.Context, request TransferAccountFundsRequest) (resp TransferAccountFundsResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupAccountSubsection, okGroupFundsTransfer, request, &resp, true)
|
||||
}
|
||||
|
||||
// AccountWithdraw withdrawal of tokens to OKCoin International or other addresses.
|
||||
func (o *OKGroup) AccountWithdraw(ctx context.Context, request AccountWithdrawRequest) (resp AccountWithdrawResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupAccountSubsection, okGroupWithdraw, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWithdrawalFee retrieves the information about the recommended network transaction fee for withdrawals to digital asset addresses. The higher the fees are, the sooner the confirmations you will get.
|
||||
func (o *OKGroup) GetAccountWithdrawalFee(ctx context.Context, currency string) (resp []GetAccountWithdrawalFeeResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v?currency=%v", okGroupGetWithdrawalFees, currency)
|
||||
} else {
|
||||
requestURL = okGroupGetAccountWalletInformation
|
||||
}
|
||||
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWithdrawalHistory retrieves all recent withdrawal records.
|
||||
func (o *OKGroup) GetAccountWithdrawalHistory(ctx context.Context, currency string) (resp []WithdrawalHistoryResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v", okGroupGetWithdrawalHistory, currency)
|
||||
} else {
|
||||
requestURL = okGroupGetWithdrawalHistory
|
||||
}
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountBillDetails retrieves the bill details of the wallet. All the information will be paged and sorted in reverse chronological order,
|
||||
// which means the latest will be at the top. Please refer to the pagination section for additional records after the first page.
|
||||
// 3 months recent records will be returned at maximum
|
||||
func (o *OKGroup) GetAccountBillDetails(ctx context.Context, request GetAccountBillDetailsRequest) (resp []GetAccountBillDetailsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupLedger, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountDepositAddressForCurrency retrieves the deposit addresses of different tokens, including previously used addresses.
|
||||
func (o *OKGroup) GetAccountDepositAddressForCurrency(ctx context.Context, currency string) (resp []GetDepositAddressResponse, _ error) {
|
||||
urlValues := url.Values{}
|
||||
urlValues.Set("currency", currency)
|
||||
requestURL := fmt.Sprintf("%v?%v", okGroupGetDepositAddress, urlValues.Encode())
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountDepositHistory retrieves the deposit history of all tokens.100 recent records will be returned at maximum
|
||||
func (o *OKGroup) GetAccountDepositHistory(ctx context.Context, currency string) (resp []GetAccountDepositHistoryResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v", OKGroupGetAccountDepositHistory, currency)
|
||||
} else {
|
||||
requestURL = OKGroupGetAccountDepositHistory
|
||||
}
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTradingAccounts retrieves the list of assets(only show pairs with balance larger than 0), the balances, amount available/on hold in spot accounts.
|
||||
func (o *OKGroup) GetSpotTradingAccounts(ctx context.Context) (resp []GetSpotTradingAccountResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, OKGroupAccounts, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTradingAccountForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account.
|
||||
func (o *OKGroup) GetSpotTradingAccountForCurrency(ctx context.Context, currency string) (resp GetSpotTradingAccountResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, currency)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotBillDetailsForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account.
|
||||
func (o *OKGroup) GetSpotBillDetailsForCurrency(ctx context.Context, request GetSpotBillDetailsForCurrencyRequest) (resp []GetSpotBillDetailsForCurrencyResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupAccounts, request.Currency, OKGroupLedger, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceSpotOrder token trading only supports limit and market orders (more order types will become available in the future).
|
||||
// You can place an order only if you have enough funds.
|
||||
// Once your order is placed, the amount will be put on hold.
|
||||
func (o *OKGroup) PlaceSpotOrder(ctx context.Context, request *PlaceOrderRequest) (resp PlaceOrderResponse, _ error) {
|
||||
if request.OrderType == "" {
|
||||
request.OrderType = strconv.Itoa(NormalOrder)
|
||||
}
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupTokenSubsection, OKGroupOrders, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMultipleSpotOrders supports placing multiple orders for specific trading pairs
|
||||
// up to 4 trading pairs, maximum 4 orders for each pair
|
||||
func (o *OKGroup) PlaceMultipleSpotOrders(ctx context.Context, request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) {
|
||||
currencyPairOrders := make(map[string]int)
|
||||
resp := make(map[string][]PlaceOrderResponse)
|
||||
|
||||
for i := range request {
|
||||
if request[i].OrderType == "" {
|
||||
request[i].OrderType = strconv.Itoa(NormalOrder)
|
||||
}
|
||||
currencyPairOrders[request[i].InstrumentID]++
|
||||
}
|
||||
|
||||
if len(currencyPairOrders) > 4 {
|
||||
return resp, []error{errors.New("up to 4 trading pairs")}
|
||||
}
|
||||
for _, orderCount := range currencyPairOrders {
|
||||
if orderCount > 4 {
|
||||
return resp, []error{errors.New("maximum 4 orders for each pair")}
|
||||
}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupTokenSubsection, OKGroupBatchOrders, request, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
var orderErrors []error
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
if !orderResponse[i].Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// CancelSpotOrder Cancelling an unfilled order.
|
||||
func (o *OKGroup) CancelSpotOrder(ctx context.Context, request CancelSpotOrderRequest) (resp CancelSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupCancelOrders, request.OrderID)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupTokenSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// CancelMultipleSpotOrders Cancelling multiple unfilled orders.
|
||||
func (o *OKGroup) CancelMultipleSpotOrders(ctx context.Context, request CancelMultipleSpotOrdersRequest) (resp map[string][]CancelMultipleSpotOrdersResponse, err error) {
|
||||
resp = make(map[string][]CancelMultipleSpotOrdersResponse)
|
||||
if len(request.OrderIDs) > 4 {
|
||||
return resp, errors.New("maximum 4 order cancellations for each pair")
|
||||
}
|
||||
|
||||
err = o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupTokenSubsection, OKGroupCancelBatchOrders, []CancelMultipleSpotOrdersRequest{request}, &resp, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
cancellationResponse := CancelMultipleSpotOrdersResponse{
|
||||
OrderID: orderResponse[i].OrderID,
|
||||
Result: orderResponse[i].Result,
|
||||
ClientOID: orderResponse[i].ClientOID,
|
||||
}
|
||||
|
||||
if !orderResponse[i].Result {
|
||||
cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency)
|
||||
}
|
||||
|
||||
resp[currency] = append(resp[currency], cancellationResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetSpotOrders List your orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotOrders(ctx context.Context, request GetSpotOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotOpenOrders List all your current open orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotOpenOrders(ctx context.Context, request GetSpotOpenOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupPendingOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotOrder Get order details by order ID.
|
||||
func (o *OKGroup) GetSpotOrder(ctx context.Context, request GetSpotOrderRequest) (resp GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v%v", OKGroupOrders, request.OrderID, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTransactionDetails Get details of the recent filled orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotTransactionDetails(ctx context.Context, request GetSpotTransactionDetailsRequest) (resp []GetSpotTransactionDetailsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupGetSpotTransactionDetails, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotTokenPairDetails Get market data. This endpoint provides the snapshots of market data and can be used without verifications.
|
||||
// List trading pairs and get the trading limit, price, and more information of different trading pairs.
|
||||
func (o *OKGroup) GetSpotTokenPairDetails(ctx context.Context) (resp []GetSpotTokenPairDetailsResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, OKGroupInstruments, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetOrderBook Getting the order book of a trading pair. Pagination is not
|
||||
// supported here. The whole book will be returned for one request. Websocket is
|
||||
// recommended here.
|
||||
func (o *OKGroup) GetOrderBook(ctx context.Context, request *GetOrderBookRequest, a asset.Item) (resp *GetOrderBookResponse, _ error) {
|
||||
var requestType, endpoint string
|
||||
switch a {
|
||||
case asset.Spot:
|
||||
endpoint = OKGroupGetSpotOrderBook
|
||||
requestType = okGroupTokenSubsection
|
||||
case asset.Futures:
|
||||
endpoint = OKGroupGetSpotOrderBook
|
||||
requestType = "futures"
|
||||
case asset.PerpetualSwap:
|
||||
endpoint = "depth"
|
||||
requestType = "swap"
|
||||
default:
|
||||
return resp, errors.New("unhandled asset type")
|
||||
}
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v",
|
||||
OKGroupInstruments,
|
||||
request.InstrumentID,
|
||||
endpoint,
|
||||
FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet,
|
||||
requestType,
|
||||
requestURL,
|
||||
nil,
|
||||
&resp,
|
||||
false)
|
||||
}
|
||||
|
||||
// GetSpotAllTokenPairsInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all trading pairs.
|
||||
func (o *OKGroup) GetSpotAllTokenPairsInformation(ctx context.Context) (resp []GetSpotTokenPairsInformationResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupInstruments, OKGroupTicker)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotAllTokenPairsInformationForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of a currency
|
||||
func (o *OKGroup) GetSpotAllTokenPairsInformationForCurrency(ctx context.Context, currency string) (resp GetSpotTokenPairsInformationResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v", OKGroupInstruments, currency, OKGroupTicker)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotFilledOrdersInformation Get the recent 60 transactions of all trading pairs.
|
||||
// Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotFilledOrdersInformation(ctx context.Context, request GetSpotFilledOrdersInformationRequest) (resp []GetSpotFilledOrdersInformationResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupTrades, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity.
|
||||
func (o *OKGroup) GetMarketData(ctx context.Context, request *GetMarketDataRequest) (resp GetMarketDataResponse, err error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotMarketData, FormatParameters(request))
|
||||
var requestType string
|
||||
switch request.Asset {
|
||||
case asset.Spot, asset.Margin:
|
||||
requestType = okGroupTokenSubsection
|
||||
case asset.Futures:
|
||||
requestType = okGroupFuturesTradingSubSection
|
||||
case asset.PerpetualSwap:
|
||||
requestType = oKGroupSwapTradingSubSection
|
||||
default:
|
||||
return nil, errors.New("asset not supported")
|
||||
}
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, requestType, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetMarginTradingAccounts List all assets under token margin trading account, including information such as balance, amount on hold and more.
|
||||
func (o *OKGroup) GetMarginTradingAccounts(ctx context.Context) (resp []GetMarginAccountsResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, OKGroupAccounts, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginTradingAccountsForCurrency Get the balance, amount on hold and more useful information.
|
||||
func (o *OKGroup) GetMarginTradingAccountsForCurrency(ctx context.Context, currency string) (resp GetMarginAccountsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, currency)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginBillDetails List all bill details. Pagination is used here.
|
||||
// before and after cursor arguments should not be confused with before and after in chronological time.
|
||||
// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginBillDetails(ctx context.Context, request GetMarginBillDetailsRequest) (resp []GetSpotBillDetailsForCurrencyResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupAccounts, request.InstrumentID, OKGroupLedger, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginAccountSettings Get all information of the margin trading account,
|
||||
// including the maximum loan amount, interest rate, and maximum leverage.
|
||||
func (o *OKGroup) GetMarginAccountSettings(ctx context.Context, currency string) (resp []GetMarginAccountSettingsResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v/%v", OKGroupAccounts, currency, okGroupGetMarketAvailability)
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetMarketAvailability)
|
||||
}
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginLoanHistory Get loan history of the margin trading account.
|
||||
// Pagination is used here. before and after cursor arguments should not be confused with before and after in chronological time.
|
||||
// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginLoanHistory(ctx context.Context, request GetMarginLoanHistoryRequest) (resp []GetMarginLoanHistoryResponse, _ error) {
|
||||
var requestURL string
|
||||
if len(request.InstrumentID) > 0 {
|
||||
requestURL = fmt.Sprintf("%v/%v/%v", OKGroupAccounts, request.InstrumentID, okGroupGetLoan)
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetLoan)
|
||||
}
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// OpenMarginLoan Borrowing tokens in a margin trading account.
|
||||
func (o *OKGroup) OpenMarginLoan(ctx context.Context, request OpenMarginLoanRequest) (resp OpenMarginLoanResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetLoan)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// RepayMarginLoan Repaying tokens in a margin trading account.
|
||||
func (o *OKGroup) RepayMarginLoan(ctx context.Context, request RepayMarginLoanRequest) (resp RepayMarginLoanResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetRepayment)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMarginOrder You can place an order only if you have enough funds. Once your order is placed, the amount will be put on hold.
|
||||
func (o *OKGroup) PlaceMarginOrder(ctx context.Context, request *PlaceOrderRequest) (resp PlaceOrderResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupMarginTradingSubsection, OKGroupOrders, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMultipleMarginOrders Place multiple orders for specific trading pairs (up to 4 trading pairs, maximum 4 orders each)
|
||||
func (o *OKGroup) PlaceMultipleMarginOrders(ctx context.Context, request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) {
|
||||
currencyPairOrders := make(map[string]int)
|
||||
resp := make(map[string][]PlaceOrderResponse)
|
||||
for i := range request {
|
||||
currencyPairOrders[request[i].InstrumentID]++
|
||||
}
|
||||
if len(currencyPairOrders) > 4 {
|
||||
return resp, []error{errors.New("up to 4 trading pairs")}
|
||||
}
|
||||
for _, orderCount := range currencyPairOrders {
|
||||
if orderCount > 4 {
|
||||
return resp, []error{errors.New("maximum 4 orders for each pair")}
|
||||
}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupMarginTradingSubsection, OKGroupBatchOrders, request, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
var orderErrors []error
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
if !orderResponse[i].Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// CancelMarginOrder Cancelling an unfilled order.
|
||||
func (o *OKGroup) CancelMarginOrder(ctx context.Context, request CancelSpotOrderRequest) (resp CancelSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupCancelOrders, request.OrderID)
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// CancelMultipleMarginOrders Cancelling multiple unfilled orders.
|
||||
func (o *OKGroup) CancelMultipleMarginOrders(ctx context.Context, request CancelMultipleSpotOrdersRequest) (map[string][]CancelMultipleSpotOrdersResponse, []error) {
|
||||
resp := make(map[string][]CancelMultipleSpotOrdersResponse)
|
||||
if len(request.OrderIDs) > 4 {
|
||||
return resp, []error{errors.New("maximum 4 order cancellations for each pair")}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, okGroupMarginTradingSubsection, OKGroupCancelBatchOrders, []CancelMultipleSpotOrdersRequest{request}, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
var orderErrors []error
|
||||
for currency, orderResponse := range resp {
|
||||
for i := range orderResponse {
|
||||
if !orderResponse[i].Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order %v for currency %v failed to be cancelled", orderResponse[i].OrderID, currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// GetMarginOrders List your orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginOrders(ctx context.Context, request GetSpotOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginOpenOrders List all your current open orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginOpenOrders(ctx context.Context, request GetSpotOpenOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupPendingOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginOrder Get order details by order ID.
|
||||
func (o *OKGroup) GetMarginOrder(ctx context.Context, request GetSpotOrderRequest) (resp GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v%v", OKGroupOrders, request.OrderID, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginTransactionDetails Get details of the recent filled orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginTransactionDetails(ctx context.Context, request GetSpotTransactionDetailsRequest) (resp []GetSpotTransactionDetailsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupGetSpotTransactionDetails, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// FormatParameters Formats URL parameters, useful for optional parameters due to OKCoin signature check
|
||||
func FormatParameters(request interface{}) (parameters string) {
|
||||
v, err := query.Values(request)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "Could not parse %v to URL values. Check that the type has url fields", reflect.TypeOf(request).Name())
|
||||
return
|
||||
}
|
||||
if urlEncodedValues := v.Encode(); len(urlEncodedValues) > 0 {
|
||||
parameters = fmt.Sprintf("?%v", urlEncodedValues)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetErrorCode returns an error code
|
||||
func (o *OKGroup) GetErrorCode(code interface{}) error {
|
||||
var assertedCode string
|
||||
|
||||
switch d := code.(type) {
|
||||
case float64:
|
||||
assertedCode = strconv.FormatFloat(d, 'f', -1, 64)
|
||||
case string:
|
||||
assertedCode = d
|
||||
default:
|
||||
return errors.New("unusual type returned")
|
||||
}
|
||||
|
||||
if i, ok := o.ErrorCodes[assertedCode]; ok {
|
||||
return i
|
||||
}
|
||||
return errors.New("unable to find SPOT error code")
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an authenticated http request to a desired
|
||||
// path with a JSON payload (of present)
|
||||
// URL arguments must be in the request path and not as url.URL values
|
||||
func (o *OKGroup) SendHTTPRequest(ctx context.Context, ep exchange.URL, httpMethod, requestType, requestPath string, data, result interface{}, authenticated bool) (err error) {
|
||||
endpoint, err := o.API.Endpoints.GetURL(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var intermediary json.RawMessage
|
||||
newRequest := func() (*request.Item, error) {
|
||||
utcTime := time.Now().UTC().Format(time.RFC3339)
|
||||
payload := []byte("")
|
||||
|
||||
if data != nil {
|
||||
payload, err = json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
path := endpoint + requestType + o.APIVersion + requestPath
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/json"
|
||||
if authenticated {
|
||||
var creds *account.Credentials
|
||||
creds, err = o.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signPath := fmt.Sprintf("/%v%v%v%v", OKGroupAPIPath,
|
||||
requestType, o.APIVersion, requestPath)
|
||||
|
||||
var hmac []byte
|
||||
hmac, err = crypto.GetHMAC(crypto.HashSHA256,
|
||||
[]byte(utcTime+httpMethod+signPath+string(payload)),
|
||||
[]byte(creds.Secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers["OK-ACCESS-KEY"] = creds.Key
|
||||
headers["OK-ACCESS-SIGN"] = crypto.Base64Encode(hmac)
|
||||
headers["OK-ACCESS-TIMESTAMP"] = utcTime
|
||||
headers["OK-ACCESS-PASSPHRASE"] = creds.ClientID
|
||||
}
|
||||
|
||||
return &request.Item{
|
||||
Method: strings.ToUpper(httpMethod),
|
||||
Path: path,
|
||||
Headers: headers,
|
||||
Body: bytes.NewBuffer(payload),
|
||||
Result: &intermediary,
|
||||
AuthRequest: authenticated,
|
||||
Verbose: o.Verbose,
|
||||
HTTPDebugging: o.HTTPDebugging,
|
||||
HTTPRecording: o.HTTPRecording,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err = o.SendPayload(ctx, request.Unset, newRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type errCapFormat struct {
|
||||
Error int64 `json:"error_code,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
Result bool `json:"result,string,omitempty"`
|
||||
}
|
||||
errCap := errCapFormat{Result: true}
|
||||
|
||||
err = json.Unmarshal(intermediary, &errCap)
|
||||
if err == nil {
|
||||
if errCap.ErrorMessage != "" {
|
||||
return fmt.Errorf("error: %v", errCap.ErrorMessage)
|
||||
}
|
||||
if errCap.Error > 0 {
|
||||
return fmt.Errorf("sendHTTPRequest error - %s",
|
||||
o.ErrorCodes[strconv.FormatInt(errCap.Error, 10)])
|
||||
}
|
||||
if !errCap.Result {
|
||||
return errors.New("unspecified error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
return json.Unmarshal(intermediary, result)
|
||||
}
|
||||
|
||||
// SetCheckVarDefaults sets main variables that will be used in requests because
|
||||
// api does not return an error if there are misspellings in strings. So better
|
||||
// to check on this, this end.
|
||||
func (o *OKGroup) SetCheckVarDefaults() {
|
||||
o.ContractTypes = []string{"this_week", "next_week", "quarter"}
|
||||
o.CurrencyPairsDefaults = []string{"btc_usd", "ltc_usd", "eth_usd", "etc_usd", "bch_usd"}
|
||||
o.Types = []string{"1min", "3min", "5min", "15min", "30min", "1day", "3day",
|
||||
"1week", "1hour", "2hour", "4hour", "6hour", "12hour"}
|
||||
o.ContractPosition = []string{"1", "2", "3", "4"}
|
||||
}
|
||||
|
||||
// GetFee returns an estimate of fee based on type of transaction
|
||||
func (o *OKGroup) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (fee float64, _ error) {
|
||||
switch feeBuilder.FeeType {
|
||||
case exchange.CryptocurrencyTradeFee:
|
||||
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker)
|
||||
case exchange.CryptocurrencyWithdrawalFee:
|
||||
withdrawFees, err := o.GetAccountWithdrawalFee(ctx, feeBuilder.FiatCurrency.String())
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
for _, withdrawFee := range withdrawFees {
|
||||
if withdrawFee.Currency == feeBuilder.FiatCurrency.String() {
|
||||
fee = withdrawFee.MinFee
|
||||
break
|
||||
}
|
||||
}
|
||||
case exchange.OfflineTradeFee:
|
||||
fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
|
||||
}
|
||||
if fee < 0 {
|
||||
fee = 0
|
||||
}
|
||||
|
||||
return fee, nil
|
||||
}
|
||||
|
||||
// getOfflineTradeFee calculates the worst case-scenario trading fee
|
||||
func getOfflineTradeFee(price, amount float64) float64 {
|
||||
return 0.0015 * price * amount
|
||||
}
|
||||
|
||||
func calculateTradingFee(purchasePrice, amount float64, isMaker bool) (fee float64) {
|
||||
// TODO volume based fees
|
||||
if isMaker {
|
||||
fee = 0.0005
|
||||
} else {
|
||||
fee = 0.0015
|
||||
}
|
||||
return fee * amount * purchasePrice
|
||||
}
|
||||
|
||||
// SetErrorDefaults sets the full error default list
|
||||
func (o *OKGroup) SetErrorDefaults() {
|
||||
o.ErrorCodes = map[string]error{
|
||||
"0": errors.New("successful"),
|
||||
"1": errors.New("invalid parameter in url normally"),
|
||||
"30001": errors.New("request header \"OK_ACCESS_KEY\" cannot be blank"),
|
||||
"30002": errors.New("request header \"OK_ACCESS_SIGN\" cannot be blank"),
|
||||
"30003": errors.New("request header \"OK_ACCESS_TIMESTAMP\" cannot be blank"),
|
||||
"30004": errors.New("request header \"OK_ACCESS_PASSPHRASE\" cannot be blank"),
|
||||
"30005": errors.New("invalid OK_ACCESS_TIMESTAMP"),
|
||||
"30006": errors.New("invalid OK_ACCESS_KEY"),
|
||||
"30007": errors.New("invalid Content_Type, please use \"application/json\" format"),
|
||||
"30008": errors.New("timestamp request expired"),
|
||||
"30009": errors.New("system error"),
|
||||
"30010": errors.New("api validation failed"),
|
||||
"30011": errors.New("invalid IP"),
|
||||
"30012": errors.New("invalid authorization"),
|
||||
"30013": errors.New("invalid sign"),
|
||||
"30014": errors.New("request too frequent"),
|
||||
"30015": errors.New("request header \"OK_ACCESS_PASSPHRASE\" incorrect"),
|
||||
"30016": errors.New("you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey"),
|
||||
"30017": errors.New("apikey's broker id does not match"),
|
||||
"30018": errors.New("apikey's domain does not match"),
|
||||
"30020": errors.New("body cannot be blank"),
|
||||
"30021": errors.New("json data format error"),
|
||||
"30023": errors.New("required parameter cannot be blank"),
|
||||
"30024": errors.New("parameter value error"),
|
||||
"30025": errors.New("parameter category error"),
|
||||
"30026": errors.New("requested too frequent; endpoint limit exceeded"),
|
||||
"30027": errors.New("login failure"),
|
||||
"30028": errors.New("unauthorized execution"),
|
||||
"30029": errors.New("account suspended"),
|
||||
"30030": errors.New("endpoint request failed. Please try again"),
|
||||
"30031": errors.New("token does not exist"),
|
||||
"30032": errors.New("pair does not exist"),
|
||||
"30033": errors.New("exchange domain does not exist"),
|
||||
"30034": errors.New("exchange ID does not exist"),
|
||||
"30035": errors.New("trading is not supported in this website"),
|
||||
"30036": errors.New("no relevant data"),
|
||||
"30037": errors.New("endpoint is offline or unavailable"),
|
||||
"30038": errors.New("user does not exist"),
|
||||
"32001": errors.New("futures account suspended"),
|
||||
"32002": errors.New("futures account does not exist"),
|
||||
"32003": errors.New("canceling, please wait"),
|
||||
"32004": errors.New("you have no unfilled orders"),
|
||||
"32005": errors.New("max order quantity"),
|
||||
"32006": errors.New("the order price or trigger price exceeds USD 1 million"),
|
||||
"32007": errors.New("leverage level must be the same for orders on the same side of the contract"),
|
||||
"32008": errors.New("max. positions to open (cross margin)"),
|
||||
"32009": errors.New("max. positions to open (fixed margin)"),
|
||||
"32010": errors.New("leverage cannot be changed with open positions"),
|
||||
"32011": errors.New("futures status error"),
|
||||
"32012": errors.New("futures order update error"),
|
||||
"32013": errors.New("token type is blank"),
|
||||
"32014": errors.New("your number of contracts closing is larger than the number of contracts available"),
|
||||
"32015": errors.New("margin ratio is lower than 100% before opening positions"),
|
||||
"32016": errors.New("margin ratio is lower than 100% after opening position"),
|
||||
"32017": errors.New("no BBO"),
|
||||
"32018": errors.New("the order quantity is less than 1, please try again"),
|
||||
"32019": errors.New("the order price deviates from the price of the previous minute by more than 3%"),
|
||||
"32020": errors.New("the price is not in the range of the price limit"),
|
||||
"32021": errors.New("leverage error"),
|
||||
"32022": errors.New("this function is not supported in your country or region according to the regulations"),
|
||||
"32023": errors.New("this account has outstanding loan"),
|
||||
"32024": errors.New("order cannot be placed during delivery"),
|
||||
"32025": errors.New("order cannot be placed during settlement"),
|
||||
"32026": errors.New("your account is restricted from opening positions"),
|
||||
"32027": errors.New("cancelled over 20 orders"),
|
||||
"32028": errors.New("account is suspended and liquidated"),
|
||||
"32029": errors.New("order info does not exist"),
|
||||
"33001": errors.New("margin account for this pair is not enabled yet"),
|
||||
"33002": errors.New("margin account for this pair is suspended"),
|
||||
"33003": errors.New("no loan balance"),
|
||||
"33004": errors.New("loan amount cannot be smaller than the minimum limit"),
|
||||
"33005": errors.New("repayment amount must exceed 0"),
|
||||
"33006": errors.New("loan order not found"),
|
||||
"33007": errors.New("status not found"),
|
||||
"33008": errors.New("loan amount cannot exceed the maximum limit"),
|
||||
"33009": errors.New("user ID is blank"),
|
||||
"33010": errors.New("you cannot cancel an order during session 2 of call auction"),
|
||||
"33011": errors.New("no new market data"),
|
||||
"33012": errors.New("order cancellation failed"),
|
||||
"33013": errors.New("order placement failed"),
|
||||
"33014": errors.New("order does not exist"),
|
||||
"33015": errors.New("exceeded maximum limit"),
|
||||
"33016": errors.New("margin trading is not open for this token"),
|
||||
"33017": errors.New("insufficient balance"),
|
||||
"33018": errors.New("this parameter must be smaller than 1"),
|
||||
"33020": errors.New("request not supported"),
|
||||
"33021": errors.New("token and the pair do not match"),
|
||||
"33022": errors.New("pair and the order do not match"),
|
||||
"33023": errors.New("you can only place market orders during call auction"),
|
||||
"33024": errors.New("trading amount too small"),
|
||||
"33025": errors.New("base token amount is blank"),
|
||||
"33026": errors.New("transaction completed"),
|
||||
"33027": errors.New("cancelled order or order cancelling"),
|
||||
"33028": errors.New("the decimal places of the trading price exceeded the limit"),
|
||||
"33029": errors.New("the decimal places of the trading size exceeded the limit"),
|
||||
"34001": errors.New("withdrawal suspended"),
|
||||
"34002": errors.New("please add a withdrawal address"),
|
||||
"34003": errors.New("sorry, this token cannot be withdrawn to xx at the moment"),
|
||||
"34004": errors.New("withdrawal fee is smaller than minimum limit"),
|
||||
"34005": errors.New("withdrawal fee exceeds the maximum limit"),
|
||||
"34006": errors.New("withdrawal amount is lower than the minimum limit"),
|
||||
"34007": errors.New("withdrawal amount exceeds the maximum limit"),
|
||||
"34008": errors.New("insufficient balance"),
|
||||
"34009": errors.New("your withdrawal amount exceeds the daily limit"),
|
||||
"34010": errors.New("transfer amount must be larger than 0"),
|
||||
"34011": errors.New("conditions not met"),
|
||||
"34012": errors.New("the minimum withdrawal amount for NEO is 1, and the amount must be an integer"),
|
||||
"34013": errors.New("please transfer"),
|
||||
"34014": errors.New("transfer limited"),
|
||||
"34015": errors.New("subaccount does not exist"),
|
||||
"34016": errors.New("transfer suspended"),
|
||||
"34017": errors.New("account suspended"),
|
||||
"34018": errors.New("incorrect trades password"),
|
||||
"34019": errors.New("please bind your email before withdrawal"),
|
||||
"34020": errors.New("please bind your funds password before withdrawal"),
|
||||
"34021": errors.New("not verified address"),
|
||||
"34022": errors.New("withdrawals are not available for sub accounts"),
|
||||
"35001": errors.New("contract subscribing does not exist"),
|
||||
"35002": errors.New("contract is being settled"),
|
||||
"35003": errors.New("contract is being paused"),
|
||||
"35004": errors.New("pending contract settlement"),
|
||||
"35005": errors.New("perpetual swap trading is not enabled"),
|
||||
"35008": errors.New("margin ratio too low when placing order"),
|
||||
"35010": errors.New("closing position size larger than available size"),
|
||||
"35012": errors.New("placing an order with less than 1 contract"),
|
||||
"35014": errors.New("order size is not in acceptable range"),
|
||||
"35015": errors.New("leverage level unavailable"),
|
||||
"35017": errors.New("changing leverage level"),
|
||||
"35019": errors.New("order size exceeds limit"),
|
||||
"35020": errors.New("order price exceeds limit"),
|
||||
"35021": errors.New("order size exceeds limit of the current tier"),
|
||||
"35022": errors.New("contract is paused or closed"),
|
||||
"35030": errors.New("place multiple orders"),
|
||||
"35031": errors.New("cancel multiple orders"),
|
||||
"35061": errors.New("invalid instrument_id"),
|
||||
}
|
||||
}
|
||||
@@ -1,810 +0,0 @@
|
||||
package okgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
// Note: GoCryptoTrader wrapper funcs currently only support SPOT trades.
|
||||
// Therefore this OKGroup_Wrapper can be shared between OKCoin and OKCoin.
|
||||
// When circumstances change, wrapper funcs can be split appropriately
|
||||
|
||||
var errNoAccountDepositAddress = errors.New("no account deposit address")
|
||||
|
||||
// Setup sets user exchange configuration settings
|
||||
func (o *OKGroup) Setup(exch *config.Exchange) error {
|
||||
err := exch.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exch.Enabled {
|
||||
o.SetEnabled(false)
|
||||
return nil
|
||||
}
|
||||
err = o.SetupDefaults(exch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wsEndpoint, err := o.API.Endpoints.GetURL(exchange.WebsocketSpot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = o.Websocket.Setup(&stream.WebsocketSetup{
|
||||
ExchangeConfig: exch,
|
||||
DefaultURL: wsEndpoint,
|
||||
RunningURL: wsEndpoint,
|
||||
Connector: o.WsConnect,
|
||||
Subscriber: o.Subscribe,
|
||||
Unsubscriber: o.Unsubscribe,
|
||||
GenerateSubscriptions: o.GenerateDefaultSubscriptions,
|
||||
Features: &o.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return o.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
RateLimit: okGroupWsRateLimit,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
})
|
||||
}
|
||||
|
||||
// FetchOrderbook returns orderbook base on the currency pair
|
||||
func (o *OKGroup) FetchOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
fPair, err := o.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ob, err := orderbook.Get(o.Name, fPair, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateOrderbook(ctx, fPair, assetType)
|
||||
}
|
||||
return ob, nil
|
||||
}
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (o *OKGroup) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.Item) (*orderbook.Base, error) {
|
||||
book := &orderbook.Base{
|
||||
Exchange: o.Name,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
VerifyOrderbook: o.CanVerifyOrderbook,
|
||||
}
|
||||
|
||||
if a == asset.Index {
|
||||
return book, errors.New("no orderbooks for index")
|
||||
}
|
||||
|
||||
fPair, err := o.FormatExchangeCurrency(p, a)
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
orderbookNew, err := o.GetOrderBook(ctx,
|
||||
&GetOrderBookRequest{
|
||||
InstrumentID: fPair.String(),
|
||||
Size: 200,
|
||||
}, a)
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
book.Bids = make(orderbook.Items, len(orderbookNew.Bids))
|
||||
for x := range orderbookNew.Bids {
|
||||
amount, convErr := strconv.ParseFloat(orderbookNew.Bids[x][1], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
price, convErr := strconv.ParseFloat(orderbookNew.Bids[x][0], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
var liquidationOrders, orderCount int64
|
||||
// Contract specific variables
|
||||
if len(orderbookNew.Bids[x]) == 4 {
|
||||
liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Bids[x][2], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
orderCount, convErr = strconv.ParseInt(orderbookNew.Bids[x][3], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
}
|
||||
|
||||
book.Bids[x] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
LiquidationOrders: liquidationOrders,
|
||||
OrderCount: orderCount,
|
||||
}
|
||||
}
|
||||
|
||||
book.Asks = make(orderbook.Items, len(orderbookNew.Asks))
|
||||
for x := range orderbookNew.Asks {
|
||||
amount, convErr := strconv.ParseFloat(orderbookNew.Asks[x][1], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
price, convErr := strconv.ParseFloat(orderbookNew.Asks[x][0], 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
var liquidationOrders, orderCount int64
|
||||
// Contract specific variables
|
||||
if len(orderbookNew.Asks[x]) == 4 {
|
||||
liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Asks[x][2], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
orderCount, convErr = strconv.ParseInt(orderbookNew.Asks[x][3], 10, 64)
|
||||
if convErr != nil {
|
||||
return book, err
|
||||
}
|
||||
}
|
||||
|
||||
book.Asks[x] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
LiquidationOrders: liquidationOrders,
|
||||
OrderCount: orderCount,
|
||||
}
|
||||
}
|
||||
|
||||
err = book.Process()
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
return orderbook.Get(o.Name, fPair, a)
|
||||
}
|
||||
|
||||
// UpdateAccountInfo retrieves balances for all enabled currencies
|
||||
func (o *OKGroup) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
|
||||
currencies, err := o.GetSpotTradingAccounts(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
|
||||
var resp account.Holdings
|
||||
resp.Exchange = o.Name
|
||||
currencyAccount := account.SubAccount{AssetType: assetType}
|
||||
|
||||
for i := range currencies {
|
||||
hold, parseErr := strconv.ParseFloat(currencies[i].Hold, 64)
|
||||
if parseErr != nil {
|
||||
return resp, parseErr
|
||||
}
|
||||
totalValue, parseErr := strconv.ParseFloat(currencies[i].Balance, 64)
|
||||
if parseErr != nil {
|
||||
return resp, parseErr
|
||||
}
|
||||
currencyAccount.Currencies = append(currencyAccount.Currencies,
|
||||
account.Balance{
|
||||
CurrencyName: currency.NewCode(currencies[i].Currency),
|
||||
Total: totalValue,
|
||||
Hold: hold,
|
||||
Free: totalValue - hold,
|
||||
})
|
||||
}
|
||||
|
||||
resp.Accounts = append(resp.Accounts, currencyAccount)
|
||||
|
||||
creds, err := o.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
err = account.Process(&resp, creds)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// FetchAccountInfo retrieves balances for all enabled currencies
|
||||
func (o *OKGroup) FetchAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
|
||||
creds, err := o.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
acc, err := account.GetHoldings(o.Name, creds, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateAccountInfo(ctx, assetType)
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// GetFundingHistory returns funding history, deposits and
|
||||
// withdrawals
|
||||
func (o *OKGroup) GetFundingHistory(ctx context.Context) (resp []exchange.FundHistory, err error) {
|
||||
accountDepositHistory, err := o.GetAccountDepositHistory(ctx, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for x := range accountDepositHistory {
|
||||
orderStatus := ""
|
||||
switch accountDepositHistory[x].Status {
|
||||
case 0:
|
||||
orderStatus = "waiting"
|
||||
case 1:
|
||||
orderStatus = "confirmation account"
|
||||
case 2:
|
||||
orderStatus = "recharge success"
|
||||
}
|
||||
|
||||
resp = append(resp, exchange.FundHistory{
|
||||
Amount: accountDepositHistory[x].Amount,
|
||||
Currency: accountDepositHistory[x].Currency,
|
||||
ExchangeName: o.Name,
|
||||
Status: orderStatus,
|
||||
Timestamp: accountDepositHistory[x].Timestamp,
|
||||
TransferID: accountDepositHistory[x].TransactionID,
|
||||
TransferType: "deposit",
|
||||
})
|
||||
}
|
||||
accountWithdrawlHistory, err := o.GetAccountWithdrawalHistory(ctx, "")
|
||||
for i := range accountWithdrawlHistory {
|
||||
resp = append(resp, exchange.FundHistory{
|
||||
Amount: accountWithdrawlHistory[i].Amount,
|
||||
Currency: accountWithdrawlHistory[i].Currency,
|
||||
ExchangeName: o.Name,
|
||||
Status: OrderStatus[accountWithdrawlHistory[i].Status],
|
||||
Timestamp: accountWithdrawlHistory[i].Timestamp,
|
||||
TransferID: accountWithdrawlHistory[i].TransactionID,
|
||||
TransferType: "withdrawal",
|
||||
})
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
func (o *OKGroup) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) {
|
||||
if err := s.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fpair, err := o.FormatExchangeCurrency(s.Pair, s.AssetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := PlaceOrderRequest{
|
||||
ClientOID: s.ClientID,
|
||||
InstrumentID: fpair.String(),
|
||||
Side: s.Side.Lower(),
|
||||
Type: s.Type.Lower(),
|
||||
Size: strconv.FormatFloat(s.Amount, 'f', -1, 64),
|
||||
}
|
||||
if s.Type == order.Limit {
|
||||
request.Price = strconv.FormatFloat(s.Price, 'f', -1, 64)
|
||||
}
|
||||
|
||||
orderResponse, err := o.PlaceSpotOrder(ctx, &request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !orderResponse.Result {
|
||||
return nil, order.ErrUnableToPlaceOrder
|
||||
}
|
||||
return s.DeriveSubmitResponse(orderResponse.OrderID)
|
||||
}
|
||||
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (o *OKGroup) ModifyOrder(_ context.Context, _ *order.Modify) (*order.ModifyResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (o *OKGroup) CancelOrder(ctx context.Context, cancel *order.Cancel) (err error) {
|
||||
err = cancel.Validate(cancel.StandardCancel())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
orderID, err := strconv.ParseInt(cancel.OrderID, 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fpair, err := o.FormatExchangeCurrency(cancel.Pair,
|
||||
cancel.AssetType)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
orderCancellationResponse, err := o.CancelSpotOrder(ctx,
|
||||
CancelSpotOrderRequest{
|
||||
InstrumentID: fpair.String(),
|
||||
OrderID: orderID,
|
||||
})
|
||||
|
||||
if !orderCancellationResponse.Result {
|
||||
err = fmt.Errorf("order %d failed to be cancelled",
|
||||
orderCancellationResponse.OrderID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
func (o *OKGroup) CancelAllOrders(ctx context.Context, orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
|
||||
if err := orderCancellation.Validate(); err != nil {
|
||||
return order.CancelAllResponse{}, err
|
||||
}
|
||||
|
||||
orderIDs := strings.Split(orderCancellation.OrderID, ",")
|
||||
resp := order.CancelAllResponse{}
|
||||
resp.Status = make(map[string]string)
|
||||
orderIDNumbers := make([]int64, 0, len(orderIDs))
|
||||
for i := range orderIDs {
|
||||
orderIDNumber, err := strconv.ParseInt(orderIDs[i], 10, 64)
|
||||
if err != nil {
|
||||
resp.Status[orderIDs[i]] = err.Error()
|
||||
continue
|
||||
}
|
||||
orderIDNumbers = append(orderIDNumbers, orderIDNumber)
|
||||
}
|
||||
|
||||
fpair, err := o.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
orderCancellation.AssetType)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
cancelOrdersResponse, err := o.CancelMultipleSpotOrders(ctx,
|
||||
CancelMultipleSpotOrdersRequest{
|
||||
InstrumentID: fpair.String(),
|
||||
OrderIDs: orderIDNumbers,
|
||||
})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
for x := range cancelOrdersResponse {
|
||||
for y := range cancelOrdersResponse[x] {
|
||||
resp.Status[strconv.FormatInt(cancelOrdersResponse[x][y].OrderID, 10)] = strconv.FormatBool(cancelOrdersResponse[x][y].Result)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetOrderInfo returns order information based on order ID
|
||||
func (o *OKGroup) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (resp order.Detail, err error) {
|
||||
if assetType != asset.Spot {
|
||||
return resp, fmt.Errorf("%s %w", assetType, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
mOrder, err := o.GetSpotOrder(ctx, GetSpotOrderRequest{OrderID: orderID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
format, err := o.GetPairFormat(assetType, false)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(mOrder.InstrumentID, format.Delimiter)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
status, err := order.StringToOrderStatus(mOrder.Status)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
|
||||
side, err := order.StringToOrderSide(mOrder.Side)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
resp = order.Detail{
|
||||
Amount: mOrder.Size,
|
||||
Pair: p,
|
||||
Exchange: o.Name,
|
||||
Date: mOrder.Timestamp,
|
||||
ExecutedAmount: mOrder.FilledSize,
|
||||
Status: status,
|
||||
Side: side,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (o *OKGroup) GetDepositAddress(ctx context.Context, c currency.Code, _, _ string) (*deposit.Address, error) {
|
||||
wallet, err := o.GetAccountDepositAddressForCurrency(ctx, c.Lower().String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(wallet) == 0 {
|
||||
return nil, fmt.Errorf("%w for currency %s",
|
||||
errNoAccountDepositAddress,
|
||||
c)
|
||||
}
|
||||
return &deposit.Address{
|
||||
Address: wallet[0].Address,
|
||||
Tag: wallet[0].Tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
// submitted
|
||||
func (o *OKGroup) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
if err := withdrawRequest.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
withdrawal, err := o.AccountWithdraw(ctx,
|
||||
AccountWithdrawRequest{
|
||||
Amount: withdrawRequest.Amount,
|
||||
Currency: withdrawRequest.Currency.Lower().String(),
|
||||
Destination: 4, // 1, 2, 3 are all internal
|
||||
Fee: withdrawRequest.Crypto.FeeAmount,
|
||||
ToAddress: withdrawRequest.Crypto.Address,
|
||||
TradePwd: withdrawRequest.TradePassword,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !withdrawal.Result {
|
||||
return nil,
|
||||
fmt.Errorf("could not withdraw currency %s to %s, no error specified",
|
||||
withdrawRequest.Currency,
|
||||
withdrawRequest.Crypto.Address)
|
||||
}
|
||||
|
||||
return &withdraw.ExchangeResponse{
|
||||
ID: strconv.FormatInt(withdrawal.WithdrawalID, 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawFiatFunds returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKGroup) WithdrawFiatFunds(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKGroup) WithdrawFiatFundsToInternationalBank(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWithdrawalsHistory returns previous withdrawals data
|
||||
func (o *OKGroup) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (o *OKGroup) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []order.Detail
|
||||
for x := range req.Pairs {
|
||||
var fPair currency.Pair
|
||||
fPair, err = o.FormatExchangeCurrency(req.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var spotOpenOrders []GetSpotOrderResponse
|
||||
spotOpenOrders, err = o.GetSpotOpenOrders(ctx,
|
||||
GetSpotOpenOrdersRequest{
|
||||
InstrumentID: fPair.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range spotOpenOrders {
|
||||
var status order.Status
|
||||
status, err = order.StringToOrderStatus(spotOpenOrders[i].Status)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var side order.Side
|
||||
side, err = order.StringToOrderSide(spotOpenOrders[i].Side)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var orderType order.Type
|
||||
orderType, err = order.StringToOrderType(spotOpenOrders[i].Type)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
resp = append(resp, order.Detail{
|
||||
OrderID: spotOpenOrders[i].OrderID,
|
||||
Price: spotOpenOrders[i].Price,
|
||||
Amount: spotOpenOrders[i].Size,
|
||||
Pair: req.Pairs[x],
|
||||
Exchange: o.Name,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
ExecutedAmount: spotOpenOrders[i].FilledSize,
|
||||
Date: spotOpenOrders[i].Timestamp,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
return req.Filter(o.Name, resp), nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (o *OKGroup) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []order.Detail
|
||||
for x := range req.Pairs {
|
||||
var fPair currency.Pair
|
||||
fPair, err = o.FormatExchangeCurrency(req.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var spotOrders []GetSpotOrderResponse
|
||||
spotOrders, err = o.GetSpotOrders(ctx,
|
||||
GetSpotOrdersRequest{
|
||||
Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"),
|
||||
InstrumentID: fPair.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range spotOrders {
|
||||
var status order.Status
|
||||
status, err = order.StringToOrderStatus(spotOrders[i].Status)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var side order.Side
|
||||
side, err = order.StringToOrderSide(spotOrders[i].Side)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
var orderType order.Type
|
||||
orderType, err = order.StringToOrderType(spotOrders[i].Type)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", o.Name, err)
|
||||
}
|
||||
detail := order.Detail{
|
||||
OrderID: spotOrders[i].OrderID,
|
||||
Price: spotOrders[i].Price,
|
||||
AverageExecutedPrice: spotOrders[i].PriceAvg,
|
||||
Amount: spotOrders[i].Size,
|
||||
ExecutedAmount: spotOrders[i].FilledSize,
|
||||
RemainingAmount: spotOrders[i].Size - spotOrders[i].FilledSize,
|
||||
Pair: req.Pairs[x],
|
||||
Exchange: o.Name,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
Date: spotOrders[i].Timestamp,
|
||||
Status: status,
|
||||
}
|
||||
detail.InferCostsAndTimes()
|
||||
resp = append(resp, detail)
|
||||
}
|
||||
}
|
||||
return req.Filter(o.Name, resp), nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (o *OKGroup) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if feeBuilder == nil {
|
||||
return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer)
|
||||
}
|
||||
if !o.AreCredentialsValid(ctx) && // Todo check connection status
|
||||
feeBuilder.FeeType == exchange.CryptocurrencyTradeFee {
|
||||
feeBuilder.FeeType = exchange.OfflineTradeFee
|
||||
}
|
||||
return o.GetFee(ctx, feeBuilder)
|
||||
}
|
||||
|
||||
// GetWithdrawCapabilities returns the types of withdrawal methods permitted by the exchange
|
||||
func (o *OKGroup) GetWithdrawCapabilities() uint32 {
|
||||
return o.GetWithdrawPermissions()
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (o *OKGroup) AuthenticateWebsocket(ctx context.Context) error {
|
||||
return o.WsLogin(ctx)
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (o *OKGroup) ValidateCredentials(ctx context.Context, assetType asset.Item) error {
|
||||
_, err := o.UpdateAccountInfo(ctx, assetType)
|
||||
return o.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func (o *OKGroup) GetHistoricTrades(_ context.Context, _ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (o *OKGroup) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if err := o.ValidateKline(pair, a, interval); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
formattedPair, err := o.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
req := &GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: start.UTC().Format(time.RFC3339),
|
||||
End: end.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: formattedPair.String(),
|
||||
}
|
||||
|
||||
candles, err := o.GetMarketData(ctx, req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
for x := range candles {
|
||||
t, ok := candles[x].([]interface{})
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unable to type asset candle data")
|
||||
}
|
||||
if len(t) < 6 {
|
||||
return kline.Item{}, errors.New("incorrect candles data length")
|
||||
}
|
||||
v, ok := t[0].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unable to type asset time data")
|
||||
}
|
||||
var tempCandle kline.Candle
|
||||
if tempCandle.Time, err = time.Parse(time.RFC3339, v); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if tempCandle.Open, err = convert.FloatFromString(t[1]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if tempCandle.High, err = convert.FloatFromString(t[2]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if tempCandle.Low, err = convert.FloatFromString(t[3]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if tempCandle.Close, err = convert.FloatFromString(t[4]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if tempCandle.Volume, err = convert.FloatFromString(t[5]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (o *OKGroup) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if err := o.ValidateKline(pair, a, interval); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
ret := kline.Item{
|
||||
Exchange: o.Name,
|
||||
Pair: pair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
formattedPair, err := o.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for x := range dates.Ranges {
|
||||
req := &GetMarketDataRequest{
|
||||
Asset: a,
|
||||
Start: dates.Ranges[x].Start.Time.UTC().Format(time.RFC3339),
|
||||
End: dates.Ranges[x].End.Time.UTC().Format(time.RFC3339),
|
||||
Granularity: o.FormatExchangeKlineInterval(interval),
|
||||
InstrumentID: formattedPair.String(),
|
||||
}
|
||||
|
||||
var candles GetMarketDataResponse
|
||||
candles, err = o.GetMarketData(ctx, req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for i := range candles {
|
||||
t, ok := candles[i].([]interface{})
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unable to type assert candles data")
|
||||
}
|
||||
if len(t) < 6 {
|
||||
return kline.Item{}, errors.New("candle data length invalid")
|
||||
}
|
||||
v, ok := t[0].(string)
|
||||
if !ok {
|
||||
return kline.Item{}, errors.New("unable to type assert time value")
|
||||
}
|
||||
var tempCandle kline.Candle
|
||||
if tempCandle.Time, err = time.Parse(time.RFC3339, v); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if tempCandle.Open, err = convert.FloatFromString(t[1]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
if tempCandle.High, err = convert.FloatFromString(t[2]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
if tempCandle.Low, err = convert.FloatFromString(t[3]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
if tempCandle.Close, err = convert.FloatFromString(t[4]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
if tempCandle.Volume, err = convert.FloatFromString(t[5]); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
ret.Candles = append(ret.Candles, tempCandle)
|
||||
}
|
||||
}
|
||||
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", o.Base.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
return ret, nil
|
||||
}
|
||||
Reference in New Issue
Block a user