Engine QA (#381)

* 1) Update Dockerfile/docker-compose.yml
2) Remove inline strings for buy/sell/test pairs
3) Remove dangerous order submission values
4) Fix consistency with audit_events (all other spec files use
CamelCase)
5) Update web websocket endpoint
6) Fix main param set (and induce dryrun mode on specific command line
params)

* Engine QA

Link up exchange syncer to cmd params, disarm market selling bombs and fix OKEX endpoints

* Fix linter issue after merge

* Engine QA changes

Template updates
Wrapper code cleanup
Disarmed order bombs
Documentation updates

* Daily engine QA

Bitstamp improvements
Spelling mistakes
Add Coinbene exchange to support list
Protect API authenticated calls for Coinbene/LBank

* Engine QA changes

Fix exchange_wrapper_coverage tool
Add SupportsAsset to exchange interface
Fix inline string usage and add BCH withdrawal support

* Engine QA

Fix Bitstamp types
Inform user of errors when parsing time accross the codebase
Change time parsing warnings to errors (as they are)
Update markdown docs [with linter fixes]

* Engine QA changes

1) Add test for dryrunParamInteraction
2) Disarm OKCoin/OKEX bombs if someone accidently sets canManipulateRealOrders to true and runs all package tests
3) Actually check exchange setup errors for BTSE and Coinbene, plus address this in the wrapper template
4) Hardcode missing/non-retrievable contributors and bump the contributors
5) Convert numbers/strings to meaningful types in Bitstamp and OKEX
6) If WS is supported for the exchange wrapper template, preset authWebsocketSupport var

* Fix the shadow people

* Link the SyncContinuously paramerino

* Also show SyncContinuously in engine.PrintSettings

* Address nitterinos and use correct filepath for logs

* Bitstamp: Extract ALL THE APM

* Fix additional nitterinos

* Fix time parsing error for Bittrex
This commit is contained in:
Adrian Gallagher
2019-11-22 16:07:30 +11:00
committed by GitHub
parent 52e2686b9e
commit 63191ce3ec
102 changed files with 3447 additions and 1714 deletions

View File

@@ -47,22 +47,22 @@ main.go
```go
var o exchange.IBotExchange
for i := range bot.exchanges {
if bot.exchanges[i].GetName() == "OKex" {
y = bot.exchanges[i]
for i := range Bot.Exchanges {
if Bot.Exchanges[i].GetName() == "OKex" {
y = Bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := o.GetTickerPrice()
tick, err := o.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := o.GetOrderbookEx()
ob, err := o.FetchOrderbook()
if err != nil {
// Handle error
}

View File

@@ -103,7 +103,7 @@ type OKGroup struct {
// GetAccountCurrencies returns a list of tradable spot instruments and their properties
func (o *OKGroup) GetAccountCurrencies() (resp []GetAccountCurrenciesResponse, _ error) {
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, false)
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, true)
}
// GetAccountWalletInformation returns a list of wallets and their properties
@@ -173,7 +173,7 @@ func (o *OKGroup) GetAccountDepositHistory(currency string) (resp []GetAccountDe
if currency != "" {
requestURL = fmt.Sprintf("%v/%v", OKGroupGetAccountDepositHistory, currency)
} else {
requestURL = okGroupGetWithdrawalHistory
requestURL = OKGroupGetAccountDepositHistory
}
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
}
@@ -198,17 +198,23 @@ func (o *OKGroup) GetSpotBillDetailsForCurrency(request GetSpotBillDetailsForCur
// 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(request *PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) {
func (o *OKGroup) PlaceSpotOrder(request *PlaceOrderRequest) (resp PlaceOrderResponse, _ error) {
if request.OrderType == "" {
request.OrderType = strconv.Itoa(NormalOrder)
}
return resp, o.SendHTTPRequest(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(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) {
func (o *OKGroup) PlaceMultipleSpotOrders(request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) {
currencyPairOrders := make(map[string]int)
resp := make(map[string][]PlaceSpotOrderResponse)
resp := make(map[string][]PlaceOrderResponse)
for i := range request {
if request[i].OrderType == "" {
request[i].OrderType = strconv.Itoa(NormalOrder)
}
currencyPairOrders[request[i].InstrumentID]++
}
@@ -422,14 +428,14 @@ func (o *OKGroup) RepayMarginLoan(request RepayMarginLoanRequest) (resp RepayMar
// PlaceMarginOrder OKEx API only supports limit and market orders (more orders 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) PlaceMarginOrder(request *PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) {
func (o *OKGroup) PlaceMarginOrder(request *PlaceOrderRequest) (resp PlaceOrderResponse, _ error) {
return resp, o.SendHTTPRequest(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(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) {
func (o *OKGroup) PlaceMultipleMarginOrders(request []PlaceOrderRequest) (map[string][]PlaceOrderResponse, []error) {
currencyPairOrders := make(map[string]int)
resp := make(map[string][]PlaceSpotOrderResponse)
resp := make(map[string][]PlaceOrderResponse)
for i := range request {
currencyPairOrders[request[i].InstrumentID]++
}
@@ -556,10 +562,7 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d
o.Name)
}
utcTime := time.Now().UTC()
iso := utcTime.String()
isoBytes := []byte(iso)
iso = string(isoBytes[:10]) + "T" + string(isoBytes[11:23]) + "Z"
utcTime := time.Now().UTC().Format(time.RFC3339)
payload := []byte("")
if data != nil {
@@ -584,11 +587,11 @@ func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, d
signPath := fmt.Sprintf("/%v%v%v%v", OKGroupAPIPath,
requestType, o.APIVersion, requestPath)
hmac := crypto.GetHMAC(crypto.HashSHA256,
[]byte(iso+httpMethod+signPath+string(payload)),
[]byte(utcTime+httpMethod+signPath+string(payload)),
[]byte(o.API.Credentials.Secret))
headers["OK-ACCESS-KEY"] = o.API.Credentials.Key
headers["OK-ACCESS-SIGN"] = crypto.Base64Encode(hmac)
headers["OK-ACCESS-TIMESTAMP"] = iso
headers["OK-ACCESS-TIMESTAMP"] = utcTime
headers["OK-ACCESS-PASSPHRASE"] = o.API.Credentials.ClientID
}

View File

@@ -6,13 +6,21 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
)
// Order types
const (
NormalOrder = iota
PostOnlyOrder
FillOrKillOrder
ImmediateOrCancelOrder
)
// GetAccountCurrenciesResponse response data for GetAccountCurrencies
type GetAccountCurrenciesResponse struct {
CanDeposit int64 `json:"can_deposit"`
CanWithdraw int64 `json:"can_withdraw"`
Currency string `json:"currency"`
MinWithdrawal float64 `json:"min_withdrawal"`
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"`
}
// WalletInformationResponse response data for WalletInformation
@@ -64,22 +72,22 @@ type AccountWithdrawResponse struct {
// GetAccountWithdrawalFeeResponse response data for GetAccountWithdrawalFee
type GetAccountWithdrawalFeeResponse struct {
Currency string `json:"currency"`
MinFee float64 `json:"min_fee"`
MaxFee float64 `json:"max_fee"`
MinFee float64 `json:"min_fee,string"`
MaxFee float64 `json:"max_fee,string"`
}
// WithdrawalHistoryResponse response data for WithdrawalHistoryResponse
type WithdrawalHistoryResponse struct {
Amount float64 `json:"amount"`
Currency string `json:"currency"`
Fee string `json:"fee"`
From string `json:"from"`
Status int64 `json:"status"`
Timestamp time.Time `json:"timestamp"`
To string `json:"to"`
Txid string `json:"txid"`
PaymentID string `json:"payment_id"`
Tag string `json:"tag"`
Amount float64 `json:"amount,string"`
Currency string `json:"currency"`
Fee string `json:"fee"`
From string `json:"from"`
Status int64 `json:"status,string"`
Timestamp time.Time `json:"timestamp"`
To string `json:"to"`
TransactionID string `json:"txid"`
PaymentID string `json:"payment_id"`
Tag string `json:"tag"`
}
// GetAccountBillDetailsRequest request data for GetAccountBillDetailsRequest
@@ -112,11 +120,12 @@ type GetDepositAddressResponse struct {
// GetAccountDepositHistoryResponse response data for GetAccountDepositHistory
type GetAccountDepositHistoryResponse struct {
Amount float64 `json:"amount"`
Amount float64 `json:"amount,string"`
Currency string `json:"currency"`
Status int64 `json:"status"`
Timestamp time.Time `json:"timestamp"`
From string `json:"from"`
To string `json:"to"`
Timestamp time.Time `json:"timestamp"`
Status int64 `json:"status,string"`
TransactionID string `json:"txid"`
}
@@ -156,20 +165,21 @@ type SpotBillDetails struct {
InstrumentID string `json:"instrument_id"`
}
// PlaceSpotOrderRequest request data for PlaceSpotOrder
type PlaceSpotOrderRequest struct {
// PlaceOrderRequest request data for placing an order
type PlaceOrderRequest struct {
ClientOID string `json:"client_oid,omitempty"` // the order ID customized by yourself
Type string `json:"type"` // limit / market(default: limit)
Side string `json:"side"` // buy or sell
InstrumentID string `json:"instrument_id"` // trading pair
MarginTrading string `json:"margin_trading"` // order type (The request value is 1)
MarginTrading string `json:"margin_trading"` // margin trading
OrderType string `json:"order_type"` // order type (0: Normal order (Unfilled and 0 imply normal limit order) 1: Post only 2: Fill or Kill 3: Immediate Or Cancel
Size string `json:"size"`
Notional string `json:"notional,omitempty"` //
Price string `json:"price,omitempty"` // price (Limit order only)
}
// PlaceSpotOrderResponse response data for PlaceSpotOrder
type PlaceSpotOrderResponse struct {
// PlaceOrderResponse response data for PlaceSpotOrder
type PlaceOrderResponse struct {
ClientOid string `json:"client_oid"`
OrderID string `json:"order_id"`
Result bool `json:"result"`
@@ -1497,7 +1507,7 @@ type WebsocketSpotOrderResponse struct {
Notional float64 `json:"notional,string"`
Size float64 `json:"size,string"`
Status string `json:"status"`
MarginTrading int64 `json:"margin_trading"`
MarginTrading int64 `json:"margin_trading,omitempty"`
Type string `json:"type"`
// Price A member, but part already exists as part of WebsocketDataResponse
// InstrumentID A member, but part already exists as part of WebsocketDataResponse

View File

@@ -267,19 +267,21 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) {
// WsLogin sends a login request to websocket to enable access to authenticated endpoints
func (o *OKGroup) WsLogin() error {
o.Websocket.SetCanUseAuthenticatedEndpoints(true)
utcTime := time.Now().UTC()
unixTime := utcTime.Unix()
unixTime := time.Now().UTC().Unix()
signPath := "/users/self/verify"
hmac := crypto.GetHMAC(crypto.HashSHA256,
[]byte(fmt.Sprintf("%v", unixTime)+http.MethodGet+signPath),
[]byte(o.API.Credentials.Secret))
[]byte(strconv.FormatInt(unixTime, 10)+http.MethodGet+signPath),
[]byte(o.API.Credentials.Secret),
)
base64 := crypto.Base64Encode(hmac)
request := WebsocketEventRequest{
Operation: "login",
Arguments: []string{o.API.Credentials.Key,
Arguments: []string{
o.API.Credentials.Key,
o.API.Credentials.ClientID,
fmt.Sprintf("%v", unixTime),
base64},
strconv.FormatInt(unixTime, 10),
base64,
},
}
err := o.WebsocketConn.SendMessage(request)
if err != nil {
@@ -470,7 +472,7 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
timeData, err := time.Parse(time.RFC3339Nano,
response.Data[i].WebsocketCandleResponse.Candle[0])
if err != nil {
log.Warnf(log.ExchangeSys,
log.Errorf(log.ExchangeSys,
"%v Time data could not be parsed: %v",
o.Name,
response.Data[i].Candle[0])

View File

@@ -236,7 +236,7 @@ func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) {
ExchangeName: o.Name,
Status: OrderStatus[accountWithdrawlHistory[i].Status],
Timestamp: accountWithdrawlHistory[i].Timestamp,
TransferID: accountWithdrawlHistory[i].Txid,
TransferID: accountWithdrawlHistory[i].TransactionID,
TransferType: "withdrawal",
})
}
@@ -255,11 +255,11 @@ func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err e
return resp, err
}
request := PlaceSpotOrderRequest{
request := PlaceOrderRequest{
ClientOID: s.ClientID,
InstrumentID: o.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
Side: strings.ToLower(s.OrderSide.String()),
Type: strings.ToLower(s.OrderType.String()),
Side: s.OrderSide.Lower(),
Type: s.OrderType.Lower(),
Size: strconv.FormatFloat(s.Amount, 'f', -1, 64),
}
if s.OrderType == order.Limit {