Add LBank exchange support (#327)

* wip

* Lbank support being added

* Lbank exchange linter issues fixed

* Removed the incomplete websocket stuff

* PR Requests completed

* PR request fixes

* Lbank Update

* Lbank Update

* Wrapper functions fixed, linter issues fixed

* Changed as per requested in PR

* Changed as per requested in PR

* Changed as per requested in PR

* Changed as per requested in PR

* PR Requests completed

* FINALLY DONE

* appveyor issues fixed

* Skip functionality for new tests

* Test functions fixed

* PR Fixes

* PR Fixes

* PR Fixes

* PR Fixes

* Final Changes

* Final Changes 2

* Final Changes 3

* Final Changes 4
This commit is contained in:
Adam
2019-08-23 11:28:38 +10:00
committed by Adrian Gallagher
parent 4f982dcdc9
commit a81ddead9e
18 changed files with 2183 additions and 26 deletions

View File

@@ -3,12 +3,13 @@ Thanks to the following contributors:
thrasher- | https://github.com/thrasher-
shazbert | https://github.com/shazbert
gloriousCode | https://github.com/gloriousCode
ermalguni | https://github.com/ermalguni
xtda | https://github.com/xtda
ermalguni | https://github.com/ermalguni
vadimzhukck | https://github.com/vadimzhukck
140am | https://github.com/140am
marcofranssen | https://github.com/marcofranssen
vadimzhukck | https://github.com/vadimzhukck
cranktakular | https://github.com/cranktakular
leilaes | https://github.com/leilaes
crackcomm | https://github.com/crackcomm
MadCozBadd | https://github.com/MadCozBadd
andreygrehov | https://github.com/andreygrehov
@@ -24,10 +25,13 @@ CodeLingoBot | https://github.com/CodeLingoBot
CodeLingoTeam | https://github.com/CodeLingoTeam
Daanikus | https://github.com/Daanikus
daniel-cohen | https://github.com/daniel-cohen
DirectX | https://github.com/DirectX
frankzougc | https://github.com/frankzougc
starit | https://github.com/starit
Jimexist | https://github.com/Jimexist
lookfirst | https://github.com/lookfirst
zeldrinn | https://github.com/zeldrinn
mattkanwisher | https://github.com/mattkanwisher
| mattkanwisher | https://github.com/mattkanwisher
| mKurrels | https://github.com/mKurrels
| m1kola | https://github.com/m1kola
| cavapoo2 | https://github.com/cavapoo2
| zeldrinn | https://github.com/zeldrinn

View File

@@ -39,6 +39,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Huobi.Hadax | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| Lbank | Yes | No | NA |
| LakeBTC | Yes | No | NA |
| LocalBitcoins | Yes | NA | NA |
| OKCoin International | Yes | Yes | No |
@@ -130,15 +131,17 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Github|Contribution Amount|
|--|--|--|
| thrasher- | https://github.com/thrasher- | 526 |
| shazbert | https://github.com/shazbert | 166 |
| gloriousCode | https://github.com/gloriousCode | 146 |
| thrasher- | https://github.com/thrasher- | 543 |
| shazbert | https://github.com/shazbert | 174 |
| gloriousCode | https://github.com/gloriousCode | 154 |
| xtda | https://github.com/xtda | 18 |
| ermalguni | https://github.com/ermalguni | 14 |
| xtda | https://github.com/xtda | 11 |
| vadimzhukck | https://github.com/vadimzhukck | 10 |
| 140am | https://github.com/140am | 8 |
| marcofranssen | https://github.com/marcofranssen | 8 |
| vadimzhukck | https://github.com/vadimzhukck | 8 |
| cranktakular | https://github.com/cranktakular | 5 |
| leilaes | https://github.com/leilaes | 3 |
| crackcomm | https://github.com/crackcomm | 3 |
| MadCozBadd | https://github.com/MadCozBadd | 2 |
| andreygrehov | https://github.com/andreygrehov | 2 |
@@ -154,15 +157,13 @@ Binaries will be published once the codebase reaches a stable condition.
| CodeLingoTeam | https://github.com/CodeLingoTeam | 1 |
| Daanikus | https://github.com/Daanikus | 1 |
| daniel-cohen | https://github.com/daniel-cohen | 1 |
| DirectX | https://github.com/DirectX | 1 |
| frankzougc | https://github.com/frankzougc | 1 |
| starit | https://github.com/starit | 1 |
| Jimexist | https://github.com/Jimexist | 1 |
| lookfirst | https://github.com/lookfirst | 1 |
| zeldrinn | https://github.com/zeldrinn | 1 |
| mattkanwisher | https://github.com/mattkanwisher | 1 |
| mKurrels | https://github.com/mKurrels | 1 |
| m1kola | https://github.com/m1kola | 1 |
| cavapoo2 | https://github.com/cavapoo2 | 1 |
| zeldrinn | https://github.com/zeldrinn | 1 |

View File

@@ -85,7 +85,6 @@ have multiple deposit accounts for different FIAT deposit currencies.
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"httpTimeout": 15000000000,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "ATENC_GBP,ATENC_NZD,BTC_AUD,BTC_SGD,LTC_BTC,START_GBP,...",

View File

@@ -13,7 +13,7 @@ import (
const (
// Default number of enabled exchanges. Modify this whenever an exchange is
// added or removed
defaultEnabledExchanges = 27
defaultEnabledExchanges = 28
)
func TestGetCurrencyConfig(t *testing.T) {
@@ -479,7 +479,7 @@ func TestCountEnabledExchanges(t *testing.T) {
}
enabledExch := GetConfigEnabledExchanges.CountEnabledExchanges()
if enabledExch != defaultEnabledExchanges {
t.Error("Test failed. GetConfigEnabledExchanges is wrong")
t.Errorf("Test failed. Expected %v, Received %v", defaultEnabledExchanges, enabledExch)
}
}

View File

@@ -1081,6 +1081,48 @@
}
]
},
{
"name": "LBank",
"enabled": true,
"verbose": false,
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
"authenticatedApiSupport": false,
"apiKey": "Key",
"apiSecret": "Secret",
"apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"proxyAddress": "",
"websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API",
"availablePairs": "fbc_usdt,hds_usdt,galt_usdt,dxn_usdt,iog_usdt,ioex_usdt,vollar_usdt,oath_usdt,bloc_usdt,btc_lbcn,eth_lbcn,usdt_lbcn,btc_usdt,eth_usdt,eth_btc,abbc_btc,bzky_eth,onot_eth,kisc_eth,bxa_usdt,atp_usdt,mat_usdt,sky_btc,sky_lbcn,rnt_usdt,vena_usdt,grin_usdt,ida_usdt,pnt_usdt,bsv_btc,bsv_usdt,opx_usdt,tena_eth,seer_lbcn,vet_lbcn,vtho_btc,vnx_lbcn,vnx_btc,amo_eth,ubex_btc,eos_btc,ubex_usdt,tns_lbcn,tns_btc,ali_eth,sdc_eth,sait_eth,artcn_usdt,dax_btc,dax_eth,dali_usdt,vet_usdt,ten_usdt,bch_usdt,neo_usdt,qtum_usdt,zec_usdt,vet_btc,pai_btc,pnt_btc,bch_btc,ltc_btc,neo_btc,dash_btc,etc_btc,qtum_btc,zec_btc,sc_btc,bts_btc,cpx_btc,xwc_btc,fil6_btc,fil12_btc,fil36_btc,eos_usdt,ut_eth,ela_eth,vet_eth,vtho_eth,pai_eth,bfdt_eth,her_eth,ptt_eth,tac_eth,idhub_eth,ssc_eth,skm_eth,iic_eth,ply_eth,ext_eth,eos_eth,yoyow_eth,trx_eth,qtum_eth,zec_eth,bts_eth,btm_eth,mith_eth,nas_eth,man_eth,dbc_eth,bto_eth,ddd_eth,cpx_eth,cs_eth,iht_eth,tky_eth,ocn_eth,dct_eth,zpt_eth,eko_eth,mda_eth,pst_eth,xwc_eth,put_eth,pnt_eth,aac_eth,fil6_eth,fil12_eth,fil36_eth,uip_eth,seer_eth,bsb_eth,cdc_eth,grams_eth,ddmx_eth,eai_eth,inc_eth,bnb_usdt,ht_usdt,bot_eth,kbc_btc,kbc_usdt,mai_usdt,phv_usdt,hnb_usdt,gt_usdt,b91_usdt,voken_usdt,cye_usdt,brc_usdt,btc_ausd",
"enabledPairs": "btc_usdt",
"baseCurrencies": "USD",
"assetTypes": "SPOT",
"supportsAutoPairUpdates": true,
"configCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
},
"requestCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
},
"bankAccounts": [
{
"bankName": "",
"bankAddress": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
},
{
"name": "LocalBitcoins",
"enabled": true,

View File

@@ -28,6 +28,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/itbit"
"github.com/thrasher-corp/gocryptotrader/exchanges/kraken"
"github.com/thrasher-corp/gocryptotrader/exchanges/lakebtc"
"github.com/thrasher-corp/gocryptotrader/exchanges/lbank"
"github.com/thrasher-corp/gocryptotrader/exchanges/localbitcoins"
"github.com/thrasher-corp/gocryptotrader/exchanges/okcoin"
"github.com/thrasher-corp/gocryptotrader/exchanges/okex"
@@ -173,6 +174,8 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
exch = new(kraken.Kraken)
case "lakebtc":
exch = new(lakebtc.LakeBTC)
case "lbank":
exch = new(lbank.Lbank)
case "localbitcoins":
exch = new(localbitcoins.LocalBitcoins)
case "okcoin international":

View File

@@ -445,3 +445,16 @@ func TestGetDepositAddress(t *testing.T) {
}
}
}
func TestUpdateOrderbook(t *testing.T) {
a.SetDefaults()
q := currency.Pair{
Delimiter: "_",
Base: currency.BTC,
Quote: currency.USD}
_, err := a.UpdateOrderbook(q, "spot")
if err != nil {
t.Fatalf("Update for orderbook failed: %v", err)
}
}

View File

@@ -787,7 +787,6 @@ func (e *Base) UpdateCurrencies(exchangeProducts currency.Pairs, enabled, force
log.Debugf("%s Updating pairs - Removed: %s.\n", e.Name, removedPairs)
}
}
if enabled {
exch.EnabledPairs = products
e.EnabledPairs = products
@@ -829,7 +828,7 @@ type Format struct {
OrderSide map[string]string
}
// CancelAllOrdersResponse returns the status from attempting to cancel all orders on an exchagne
// CancelAllOrdersResponse returns the status from attempting to cancel all orders on an exchange
type CancelAllOrdersResponse struct {
OrderStatus map[string]string
}
@@ -853,7 +852,7 @@ const (
// ToString changes the ordertype to the exchange standard and returns a string
func (o OrderType) ToString() string {
return fmt.Sprintf("%v", o)
return string(o)
}
// OrderSide enforces a standard for OrderSides across the code base
@@ -870,7 +869,7 @@ const (
// ToString changes the ordertype to the exchange standard and returns a string
func (o OrderSide) ToString() string {
return fmt.Sprintf("%v", o)
return string(o)
}
// SetAPIURL sets configuration API URL for an exchange

133
exchanges/lbank/README.md Normal file
View File

@@ -0,0 +1,133 @@
# GoCryptoTrader package Lbank
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/lbank)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
This lbank package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progresss 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://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY)
## Lbank Exchange
### Current Features
+ REST Support
### How to enable
+ [Enable via configuration](https://githul.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 l exchange.IBotExchange
for i := range bot.exchanges {
if bot.exchanges[i].GetName() == "Lbank" {
l = bot.exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := l.GetTickerPrice()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := l.GetOrderbookEx()
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 := l.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := l.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := l.GetOrderBook()
if err != nil {
// Handle error
}
// Private calls - make sure your APIKEY and APISECRET are set and
// AuthenticatedAPISupport is set to true
// GetUserInfo returns account info
accountInfo, err := l.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := l.Trade(...)
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:
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***

622
exchanges/lbank/lbank.go Normal file
View File

@@ -0,0 +1,622 @@
package lbank
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
// Lbank is the overarching type across this package
type Lbank struct {
exchange.Base
privateKey *rsa.PrivateKey
WebsocketConn *wshandler.WebsocketConnection
}
const (
lbankAPIURL = "https://api.lbkex.com"
lbankAPIVersion = "1"
lbankAuthRateLimit = 0
lbankUnAuthRateLimit = 0
lbankFeeNotFound = 0.0
// Public endpoints
lbankTicker = "ticker.do"
lbankCurrencyPairs = "currencyPairs.do"
lbankMarketDepths = "depth.do"
lbankTrades = "trades.do"
lbankKlines = "kline.do"
lbankPairInfo = "accuracy.do"
lbankUSD2CNYRate = "usdToCny.do"
lbankWithdrawConfig = "withdrawConfigs.do"
// Authenticated endpoints
lbankUserInfo = "user_info.do"
lbankPlaceOrder = "create_order.do"
lbankCancelOrder = "cancel_order.do"
lbankQueryOrder = "orders_info.do"
lbankQueryHistoryOrder = "orders_info_history.do"
lbankOrderTransactionDetails = "order_transaction_detail.do"
lbankPastTransactions = "transaction_history.do"
lbankOpeningOrders = "orders_info_no_deal.do"
lbankWithdrawalRecords = "withdraws.do"
lbankWithdraw = "withdraw.do"
lbankRevokeWithdraw = "withdrawCancel.do"
)
// SetDefaults sets the basic defaults for Lbank
func (l *Lbank) SetDefaults() {
l.Name = "Lbank"
l.RESTPollingDelay = 10
l.RequestCurrencyPairFormat.Delimiter = "_"
l.ConfigCurrencyPairFormat.Delimiter = "_"
l.AssetTypes = []string{ticker.Spot}
l.SupportsAutoPairUpdating = true
l.APIWithdrawPermissions = exchange.AutoWithdrawCryptoWithAPIPermission | exchange.NoFiatWithdrawals
l.Requester = request.New(l.Name,
request.NewRateLimit(time.Second, lbankAuthRateLimit),
request.NewRateLimit(time.Second, lbankUnAuthRateLimit),
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
l.APIUrlDefault = lbankAPIURL
l.APIUrl = l.APIUrlDefault
l.Websocket = wshandler.New()
}
// Setup takes in the supplied exchange configuration details and sets params
func (l *Lbank) Setup(exch *config.ExchangeConfig) {
if !exch.Enabled {
l.SetEnabled(false)
} else {
l.Enabled = true
l.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
l.AuthenticatedWebsocketAPISupport = exch.AuthenticatedWebsocketAPISupport
l.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
l.SetHTTPClientTimeout(exch.HTTPTimeout)
l.SetHTTPClientUserAgent(exch.HTTPUserAgent)
l.RESTPollingDelay = exch.RESTPollingDelay
l.Verbose = exch.Verbose
l.Websocket.SetWsStatusAndConnection(exch.Websocket)
l.BaseCurrencies = exch.BaseCurrencies
l.AvailablePairs = exch.AvailablePairs
l.EnabledPairs = exch.EnabledPairs
err := l.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = l.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
err = l.SetAutoPairDefaults()
if err != nil {
log.Fatal(err)
}
err = l.SetAPIURL(exch)
if err != nil {
log.Fatal(err)
}
err = l.SetClientProxyAddress(exch.ProxyAddress)
if err != nil {
log.Fatal(err)
}
if l.AuthenticatedAPISupport {
err = l.loadPrivKey()
if err != nil {
l.AuthenticatedAPISupport = false
log.Errorf("couldnt load private key, setting authenticated support to false")
}
}
}
}
// GetTicker returns a ticker for the specified symbol
// symbol: eth_btc
func (l *Lbank) GetTicker(symbol string) (TickerResponse, error) {
var t TickerResponse
params := url.Values{}
params.Set("symbol", symbol)
path := fmt.Sprintf("%s/v%s/%s?%s", l.APIUrl, lbankAPIVersion, lbankTicker, params.Encode())
return t, l.SendHTTPRequest(path, &t)
}
// GetCurrencyPairs returns a list of supported currency pairs by the exchange
func (l *Lbank) GetCurrencyPairs() ([]string, error) {
path := fmt.Sprintf("%s/v%s/%s", l.APIUrl, lbankAPIVersion,
lbankCurrencyPairs)
var result []string
return result, l.SendHTTPRequest(path, &result)
}
// GetMarketDepths returns arrays of asks, bids and timestamp
func (l *Lbank) GetMarketDepths(symbol, size, merge string) (MarketDepthResponse, error) {
var m MarketDepthResponse
params := url.Values{}
params.Set("symbol", symbol)
params.Set("size", size)
params.Set("merge", merge)
path := fmt.Sprintf("%s/v%s/%s?%s", l.APIUrl, lbankAPIVersion, lbankMarketDepths, params.Encode())
return m, l.SendHTTPRequest(path, &m)
}
// GetTrades returns an array of available trades regarding a particular exchange
func (l *Lbank) GetTrades(symbol, size, time string) ([]TradeResponse, error) {
var g []TradeResponse
params := url.Values{}
params.Set("symbol", symbol)
params.Set("size", size)
params.Set("time", time)
path := fmt.Sprintf("%s/v%s/%s?%s", l.APIUrl, lbankAPIVersion, lbankTrades, params.Encode())
return g, l.SendHTTPRequest(path, &g)
}
// GetKlines returns kline data
func (l *Lbank) GetKlines(symbol, size, klineType, time string) ([]KlineResponse, error) {
var klineTemp interface{}
var k []KlineResponse
params := url.Values{}
params.Set("symbol", symbol)
params.Set("size", size)
params.Set("type", klineType)
params.Set("time", time)
path := fmt.Sprintf("%s/v%s/%s?%s", l.APIUrl, lbankAPIVersion, lbankKlines, params.Encode())
err := l.SendHTTPRequest(path, &klineTemp)
if err != nil {
return k, err
}
resp, ok := klineTemp.([]interface{})
if !ok {
return nil, errors.New("response received is invalid")
}
for i := range resp {
resp2, ok := resp[i].([]interface{})
if !ok {
return nil, errors.New("response received is invalid")
}
var tempResp KlineResponse
for x := range resp2 {
switch x {
case 0:
tempResp.TimeStamp = int64(resp2[x].(float64))
case 1:
if val, ok := resp2[x].(int64); ok {
tempResp.OpenPrice = float64(val)
} else {
tempResp.OpenPrice = resp2[x].(float64)
}
case 2:
if val, ok := resp2[x].(int64); ok {
tempResp.HigestPrice = float64(val)
} else {
tempResp.HigestPrice = resp2[x].(float64)
}
case 3:
if val, ok := resp2[x].(int64); ok {
tempResp.LowestPrice = float64(val)
} else {
tempResp.LowestPrice = resp2[x].(float64)
}
case 4:
if val, ok := resp2[x].(int64); ok {
tempResp.ClosePrice = float64(val)
} else {
tempResp.ClosePrice = resp2[x].(float64)
}
case 5:
if val, ok := resp2[x].(int64); ok {
tempResp.TradingVolume = float64(val)
} else {
tempResp.TradingVolume = resp2[x].(float64)
}
}
}
k = append(k, tempResp)
}
return k, nil
}
// GetUserInfo gets users account info
func (l *Lbank) GetUserInfo() (InfoFinalResponse, error) {
var resp InfoFinalResponse
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankUserInfo)
err := l.SendAuthHTTPRequest(http.MethodPost, path, nil, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// CreateOrder creates an order
func (l *Lbank) CreateOrder(pair, side string, amount, price float64) (CreateOrderResponse, error) {
var resp CreateOrderResponse
if !strings.EqualFold(side, "buy") && !strings.EqualFold(side, "sell") {
return resp, errors.New("side type invalid can only be 'buy' or 'sell'")
}
if amount <= 0 {
return resp, errors.New("amount can't be smaller than or equal to 0")
}
if price <= 0 {
return resp, errors.New("price can't be smaller than or equal to 0")
}
params := url.Values{}
params.Set("symbol", pair)
params.Set("type", common.StringToLower(side))
params.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankPlaceOrder)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// RemoveOrder cancels a given order
func (l *Lbank) RemoveOrder(pair, orderID string) (RemoveOrderResponse, error) {
var resp RemoveOrderResponse
params := url.Values{}
params.Set("symbol", pair)
params.Set("order_id", orderID)
path := fmt.Sprintf("%s/v%s/%s", l.APIUrl, lbankAPIVersion, lbankCancelOrder)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// QueryOrder finds out information about orders (can pass up to 3 comma separated values to this)
// Lbank returns an empty string as their []OrderResponse instead of returning an empty array, so when len(tempResp.Orders) > 2 its not empty and should be unmarshalled separately
func (l *Lbank) QueryOrder(pair, orderIDs string) (QueryOrderFinalResponse, error) {
var resp QueryOrderFinalResponse
var tempResp QueryOrderResponse
params := url.Values{}
params.Set("symbol", pair)
params.Set("order_id", orderIDs)
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankQueryOrder)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &tempResp)
if err != nil {
return resp, err
}
var totalOrders []OrderResponse
if len(tempResp.Orders) > 2 {
err = json.Unmarshal(tempResp.Orders, &totalOrders)
if err != nil {
return resp, err
}
}
resp.ErrCapture = tempResp.ErrCapture
resp.Orders = totalOrders
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// QueryOrderHistory finds order info in the past 2 days
// Lbank returns an empty string as their []OrderResponse instead of returning an empty array, so when len(tempResp.Orders) > 2 its not empty and should be unmarshalled separately
func (l *Lbank) QueryOrderHistory(pair, pageNumber, pageLength string) (OrderHistoryFinalResponse, error) {
var resp OrderHistoryFinalResponse
var tempResp OrderHistoryResponse
params := url.Values{}
params.Set("symbol", pair)
params.Set("current_page", pageNumber)
params.Set("page_length", pageLength)
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankQueryHistoryOrder)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &tempResp)
if err != nil {
return resp, err
}
var totalOrders []OrderResponse
if len(tempResp.Orders) > 2 {
err = json.Unmarshal(tempResp.Orders, &totalOrders)
if err != nil {
return resp, err
}
}
resp.ErrCapture = tempResp.ErrCapture
resp.PageLength = tempResp.PageLength
resp.Orders = totalOrders
resp.CurrentPage = tempResp.CurrentPage
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// GetPairInfo finds information about all trading pairs
func (l *Lbank) GetPairInfo() ([]PairInfoResponse, error) {
var resp []PairInfoResponse
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankPairInfo)
return resp, l.SendHTTPRequest(path, &resp)
}
// OrderTransactionDetails gets info about transactions
func (l *Lbank) OrderTransactionDetails(symbol, orderID string) (TransactionHistoryResp, error) {
var resp TransactionHistoryResp
params := url.Values{}
params.Set("symbol", symbol)
params.Set("order_id", orderID)
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankOrderTransactionDetails)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// TransactionHistory stores info about transactions
func (l *Lbank) TransactionHistory(symbol, transactionType, startDate, endDate, from, direct, size string) (TransactionHistoryResp, error) {
var resp TransactionHistoryResp
params := url.Values{}
params.Set("symbol", symbol)
params.Set("type", transactionType)
params.Set("start_date", startDate)
params.Set("end_date", endDate)
params.Set("from", from)
params.Set("direct", direct)
params.Set("size", size)
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankPastTransactions)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// GetOpenOrders gets opening orders
// Lbank returns an empty string as their []OrderResponse instead of returning an empty array, so when len(tempResp.Orders) > 2 its not empty and should be unmarshalled separately
func (l *Lbank) GetOpenOrders(pair, pageNumber, pageLength string) (OpenOrderFinalResponse, error) {
var resp OpenOrderFinalResponse
var tempResp OpenOrderResponse
params := url.Values{}
params.Set("symbol", pair)
params.Set("current_page", pageNumber)
params.Set("page_length", pageLength)
path := fmt.Sprintf("%s/v%s/%s", l.APIUrl, lbankAPIVersion, lbankOpeningOrders)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &tempResp)
if err != nil {
return resp, err
}
var totalOrders []OrderResponse
if len(tempResp.Orders) > 2 {
err = json.Unmarshal(tempResp.Orders, &totalOrders)
if err != nil {
return resp, err
}
}
resp.ErrCapture = tempResp.ErrCapture
resp.PageLength = tempResp.PageLength
resp.PageNumber = tempResp.PageNumber
resp.Orders = totalOrders
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// USD2RMBRate finds USD-CNY Rate
func (l *Lbank) USD2RMBRate() (ExchangeRateResponse, error) {
var resp ExchangeRateResponse
path := fmt.Sprintf("%s/v%s/%s", l.APIUrl, lbankAPIVersion, lbankUSD2CNYRate)
return resp, l.SendHTTPRequest(path, &resp)
}
// GetWithdrawConfig gets information about withdrawals
func (l *Lbank) GetWithdrawConfig(assetCode string) ([]WithdrawConfigResponse, error) {
var resp []WithdrawConfigResponse
params := url.Values{}
params.Set("assetCode", assetCode)
path := fmt.Sprintf("%s/v%s/%s?%s", l.APIUrl, lbankAPIVersion, lbankWithdrawConfig, params.Encode())
return resp, l.SendHTTPRequest(path, &resp)
}
// Withdraw sends a withdrawal request
func (l *Lbank) Withdraw(account, assetCode, amount, memo, mark string) (WithdrawResponse, error) {
var resp WithdrawResponse
params := url.Values{}
params.Set("account", account)
params.Set("assetCode", assetCode)
params.Set("amount", amount)
if memo != "" {
params.Set("memo", memo)
}
if mark != "" {
params.Set("mark", mark)
}
path := fmt.Sprintf("%s/v%s/%s", l.APIUrl, lbankAPIVersion, lbankWithdraw)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// RevokeWithdraw cancels the withdrawal given the withdrawalID
func (l *Lbank) RevokeWithdraw(withdrawID string) (RevokeWithdrawResponse, error) {
var resp RevokeWithdrawResponse
params := url.Values{}
if withdrawID != "" {
params.Set("withdrawId", withdrawID)
}
path := fmt.Sprintf("%s/v%s/%s?", l.APIUrl, lbankAPIVersion, lbankRevokeWithdraw)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// GetWithdrawalRecords gets withdrawal records
func (l *Lbank) GetWithdrawalRecords(assetCode, status, pageNo, pageSize string) (WithdrawalResponse, error) {
var resp WithdrawalResponse
params := url.Values{}
params.Set("assetCode", assetCode)
params.Set("status", status)
params.Set("pageNo", pageNo)
params.Set("pageSize", pageSize)
path := fmt.Sprintf("%s/v%s/%s", l.APIUrl, lbankAPIVersion, lbankWithdrawalRecords)
err := l.SendAuthHTTPRequest(http.MethodPost, path, params, &resp)
if err != nil {
return resp, err
}
if resp.Error != 0 {
return resp, ErrorCapture(resp.Error)
}
return resp, nil
}
// ErrorCapture captures errors
func ErrorCapture(code int64) error {
msg, ok := errorCodes[code]
if !ok {
return fmt.Errorf("undefined code please check api docs for error code definition: %v", code)
}
return errors.New(msg)
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (l *Lbank) SendHTTPRequest(path string, result interface{}) error {
return l.SendPayload(http.MethodGet, path, nil, nil, &result, false, false, l.Verbose, l.HTTPDebugging)
}
func (l *Lbank) loadPrivKey() error {
key := strings.Join([]string{
"-----BEGIN RSA PRIVATE KEY-----",
l.APISecret,
"-----END RSA PRIVATE KEY-----",
}, "\n")
block, _ := pem.Decode([]byte(key))
if block == nil {
return errors.New("pem block is nil")
}
p, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf("unable to decode priv key: %s", err)
}
var ok bool
l.privateKey, ok = p.(*rsa.PrivateKey)
if !ok {
return errors.New("unable to parse RSA private key")
}
return nil
}
func (l *Lbank) sign(data string) (string, error) {
if l.privateKey == nil {
return "", errors.New("private key not loaded")
}
md5hash := common.GetMD5([]byte(data))
m := common.StringToUpper(common.HexEncodeToString(md5hash))
s := common.GetSHA256([]byte(m))
r, err := rsa.SignPKCS1v15(rand.Reader, l.privateKey, crypto.SHA256, s)
if err != nil {
return "", err
}
return common.Base64Encode(r), nil
}
// SendAuthHTTPRequest sends an authenticated request
func (l *Lbank) SendAuthHTTPRequest(method, endpoint string, vals url.Values, result interface{}) error {
if vals == nil {
vals = url.Values{}
}
vals.Set("api_key", l.APIKey)
sig, err := l.sign(vals.Encode())
if err != nil {
return err
}
vals.Set("sign", sig)
payload := vals.Encode()
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
return l.SendPayload(method,
endpoint,
headers,
bytes.NewBufferString(payload),
&result,
true,
false,
l.Verbose,
l.HTTPDebugging)
}

View File

@@ -0,0 +1,387 @@
package lbank
import (
"fmt"
"sync"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
)
// Please supply your own keys here for due diligence testing
const (
testAPIKey = ""
testAPISecret = ""
canManipulateRealOrders = false
)
var l Lbank
var setupRan bool
var m sync.Mutex
func TestSetup(t *testing.T) {
t.Parallel()
m.Lock()
defer m.Unlock()
if setupRan {
return
}
l.SetDefaults()
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json")
if err != nil {
t.Errorf("Test Failed - Lbank Setup() init error:, %v", err)
}
lbankConfig, err := cfg.GetExchangeConfig("Lbank")
if err != nil {
t.Errorf("Test Failed - Lbank Setup() init error: %v", err)
}
lbankConfig.Websocket = true
lbankConfig.AuthenticatedAPISupport = true
lbankConfig.APISecret = testAPISecret
lbankConfig.APIKey = testAPIKey
l.Setup(&lbankConfig)
setupRan = true
}
func areTestAPIKeysSet() bool {
if l.APIKey != "" && l.APIKey != "Key" &&
l.APISecret != "" && l.APISecret != "Secret" {
return true
}
return false
}
func TestGetTicker(t *testing.T) {
TestSetup(t)
_, err := l.GetTicker("btc_usdt")
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestGetCurrencyPairs(t *testing.T) {
TestSetup(t)
_, err := l.GetCurrencyPairs()
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestGetMarketDepths(t *testing.T) {
TestSetup(t)
_, err := l.GetMarketDepths("btc_usdt", "60", "1")
if err != nil {
t.Errorf("GetMarketDepth failed: %v", err)
}
a, _ := l.GetMarketDepths("btc_usdt", "60", "0")
if len(a.Asks) != 60 {
t.Errorf("length requested doesnt match the output")
}
}
func TestGetTrades(t *testing.T) {
TestSetup(t)
_, err := l.GetTrades("btc_usdt", "600", fmt.Sprintf("%v", time.Now().Unix()))
if err != nil {
t.Errorf("test failed: %v", err)
}
a, err := l.GetTrades("btc_usdt", "600", "0")
if len(a) != 600 && err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestGetKlines(t *testing.T) {
TestSetup(t)
_, err := l.GetKlines("btc_usdt", "600", "minute1", fmt.Sprintf("%v", time.Now().Unix()))
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestUpdateOrderbook(t *testing.T) {
TestSetup(t)
p := currency.Pair{
Delimiter: "_",
Base: currency.ETH,
Quote: currency.BTC}
_, err := l.UpdateOrderbook(p.Lower(), "spot")
if err != nil {
t.Errorf("Update for orderbook failed: %v", err)
}
}
func TestGetUserInfo(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := l.GetUserInfo()
if err != nil {
t.Errorf("invalid key or sign: %v", err)
}
}
func TestCreateOrder(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_")
_, err := l.CreateOrder(cp.Lower().String(), "what", 1231, 12314)
if err == nil {
t.Error("Test Failed - CreateOrder error cannot be nil")
}
_, err = l.CreateOrder(cp.Lower().String(), "buy", 0, 0)
if err == nil {
t.Error("Test Failed - CreateOrder error cannot be nil")
}
_, err = l.CreateOrder(cp.Lower().String(), "sell", 1231, 0)
if err == nil {
t.Error("Test Failed - CreateOrder error cannot be nil")
}
_, err = l.CreateOrder(cp.Lower().String(), "buy", 58, 681)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestRemoveOrder(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
cp := currency.NewPairWithDelimiter(currency.ETH.String(), currency.BTC.String(), "_")
_, err := l.RemoveOrder(cp.Lower().String(), "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23")
if err != nil {
t.Errorf("unable to remove order: %v", err)
}
}
func TestQueryOrder(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_")
_, err := l.QueryOrder(cp.Lower().String(), "1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
func TestQueryOrderHistory(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_")
_, err := l.QueryOrderHistory(cp.Lower().String(), "1", "100")
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestGetPairInfo(t *testing.T) {
TestSetup(t)
_, err := l.GetPairInfo()
if err != nil {
t.Errorf("couldnt get pair info: %v", err)
}
}
func TestOrderTransactionDetails(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := l.OrderTransactionDetails("eth_btc", "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23")
if err != nil {
t.Errorf("couldnt get transaction details: %v", err)
}
}
func TestTransactionHistory(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := l.TransactionHistory("btc_usdt", "", "", "", "", "", "")
if err != nil {
t.Errorf("couldnt get transaction history: %v", err)
}
}
func TestGetOpenOrders(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_")
_, err := l.GetOpenOrders(cp.Lower().String(), "1", "50")
if err != nil {
t.Error("unexpected error", err)
}
}
func TestUSD2RMBRate(t *testing.T) {
TestSetup(t)
_, err := l.USD2RMBRate()
if err != nil {
t.Error("unable to acquire the rate")
}
}
func TestGetWithdrawConfig(t *testing.T) {
TestSetup(t)
_, err := l.GetWithdrawConfig("eth")
if err != nil {
t.Errorf("unable to get withdraw config: %v", err)
}
}
func TestWithdraw(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := l.Withdraw("", "", "", "", "")
if err != nil {
t.Errorf("unable to withdraw: %v", err)
}
}
func TestGetWithdrawRecords(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := l.GetWithdrawalRecords("eth", "0", "1", "20")
if err != nil {
t.Errorf("unable to get withdrawal records: %v", err)
}
}
func TestLoadPrivKey(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
err := l.loadPrivKey()
if err != nil {
t.Error(err)
}
l.APISecret = "errortest"
err = l.loadPrivKey()
if err == nil {
t.Errorf("expected error due to pemblock nil, got err: %v", err)
}
}
func TestSign(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
l.APISecret = testAPISecret
l.loadPrivKey()
_, err := l.sign("hello123")
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestSubmitOrder(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_")
_, err := l.SubmitOrder(cp.Lower(), "BUY", "ANY", 2, 1312, "")
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestCancelOrder(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
cp := currency.NewPairWithDelimiter(currency.ETH.String(), currency.BTC.String(), "_")
var a exchange.OrderCancellation
a.CurrencyPair = cp
a.OrderID = "24f7ce27-af1d-4dca-a8c1-ef1cbeec1b23"
err := l.CancelOrder(&a)
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestGetOrderInfo(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := l.GetOrderInfo("9ead39f5-701a-400b-b635-d7349eb0f6b")
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestGetAllOpenOrderID(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := l.getAllOpenOrderID()
if err != nil {
t.Errorf("test failed: %v", err)
}
}
func TestGetFeeByType(t *testing.T) {
TestSetup(t)
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_")
var input exchange.FeeBuilder
input.Amount = 2
input.FeeType = exchange.CryptocurrencyWithdrawalFee
input.Pair = cp
a, err := l.GetFeeByType(&input)
if err != nil {
t.Errorf("test failed. couldnt get fee: %v", err)
}
if a != 0.0005 {
t.Errorf("testGetFeeByType failed. Expected: 0.0005, Received: %v", a)
}
}
func TestGetAccountInfo(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := l.GetAccountInfo()
if err != nil {
t.Error(err)
}
}
func TestGetOrderHistory(t *testing.T) {
TestSetup(t)
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
var input exchange.GetOrdersRequest
input.OrderSide = exchange.BuyOrderSide
_, err := l.GetOrderHistory(&input)
if err != nil {
t.Error(err)
}
}

View File

@@ -0,0 +1,270 @@
package lbank
import (
"encoding/json"
)
// Ticker stores the ticker price data for a currency pair
type Ticker struct {
Change float64 `json:"change"`
High float64 `json:"high"`
Latest float64 `json:"latest"`
Low float64 `json:"low"`
Turnover float64 `json:"turnover"`
Volume float64 `json:"vol"`
}
// TickerResponse stores the ticker price data and timestamp for a currency pair
type TickerResponse struct {
Symbol string `json:"symbol"`
Timestamp int64 `json:"timestamp"`
Ticker Ticker `json:"ticker"`
}
// MarketDepthResponse stores arrays for asks, bids and a timestamp for a currecy pair
type MarketDepthResponse struct {
ErrCapture `json:",omitempty"`
Asks [][]float64 `json:"asks"`
Bids [][]float64 `json:"bids"`
Timestamp int64 `json:"timestamp"`
}
// TradeResponse stores date_ms, amount, price, type, tid for a currency pair
type TradeResponse struct {
DateMS int64 `json:"date_ms"`
Amount float64 `json:"amount"`
Price float64 `json:"price"`
Type string `json:"type"`
TID string `json:"tid"`
}
// KlineResponse stores kline info for given currency exchange
type KlineResponse struct {
TimeStamp int64 `json:"timestamp"`
OpenPrice float64 `json:"openprice"`
HigestPrice float64 `json:"highestprice"`
LowestPrice float64 `json:"lowestprice"`
ClosePrice float64 `json:"closeprice"`
TradingVolume float64 `json:"tradingvolume"`
}
// InfoResponse stores info
type InfoResponse struct {
Freeze map[string]string `json:"freeze"`
Asset map[string]string `json:"asset"`
Free map[string]string `json:"Free"`
}
// InfoFinalResponse stores info
type InfoFinalResponse struct {
ErrCapture `json:",omitempty"`
Info InfoResponse `json:"info"`
}
// CreateOrderResponse stores the result of the Order and
type CreateOrderResponse struct {
ErrCapture `json:",omitempty"`
OrderID string `json:"order_id"`
}
// RemoveOrderResponse stores the result when an order is cancelled
type RemoveOrderResponse struct {
ErrCapture `json:",omitempty"`
Err string `json:"error"`
OrderID string `json:"order_id"`
Success string `json:"success"`
}
// OrderResponse stores the data related to the given OrderIDs
type OrderResponse struct {
Symbol string `json:"symbol"`
Amount float64 `json:"amount"`
CreateTime int64 `json:"created_time"`
Price float64 `json:"price"`
AvgPrice float64 `json:"avg_price"`
Type string `json:"type"`
OrderID string `json:"order_id"`
DealAmount float64 `json:"deal_amount"`
Status int64 `json:"status"`
}
// QueryOrderResponse stores the data from queries
type QueryOrderResponse struct {
ErrCapture `json:",omitempty"`
Orders json.RawMessage `json:"orders"`
}
// QueryOrderFinalResponse stores data from queries
type QueryOrderFinalResponse struct {
ErrCapture
Orders []OrderResponse
}
// OrderHistory stores data for past orders
type OrderHistory struct {
Result bool `json:"result,string"`
Total string `json:"total"`
PageLength uint8 `json:"page_length"`
Orders json.RawMessage `json:"orders"`
CurrentPage uint8 `json:"current_page"`
ErrorCode int64 `json:"error_code"`
}
// OrderHistoryResponse stores past orders
type OrderHistoryResponse struct {
ErrCapture `json:",omitempty"`
PageLength uint8 `json:"page_length"`
Orders json.RawMessage `json:"orders"`
CurrentPage uint8 `json:"current_page"`
}
// OrderHistoryFinalResponse stores past orders
type OrderHistoryFinalResponse struct {
ErrCapture
PageLength uint8
Orders []OrderResponse
CurrentPage uint8
}
// PairInfoResponse stores information about trading pairs
type PairInfoResponse struct {
MinimumQuantity string `json:"minTranQua"`
PriceAccuracy string `json:"priceAccuracy"`
QuantityAccuracy string `json:"quantityAccuracy"`
Symbol string `json:"symbol"`
}
// TransactionTemp stores details about transactions
type TransactionTemp struct {
TxUUID string `json:"txUuid"`
OrderUUID string `json:"orderUuid"`
TradeType string `json:"tradeType"`
DealTime int64 `json:"dealTime"`
DealPrice float64 `json:"dealPrice"`
DealQuantity float64 `json:"dealQuantity"`
DealVolPrice float64 `json:"dealVolumePrice"`
TradeFee float64 `json:"tradeFee"`
TradeFeeRate float64 `json:"tradeFeeRate"`
}
// TransactionHistoryResp stores details about past transactions
type TransactionHistoryResp struct {
ErrCapture `json:",omitempty"`
Transaction []TransactionTemp `json:"transaction"`
}
// OpenOrderResponse stores information about the opening orders
type OpenOrderResponse struct {
ErrCapture `json:",omitempty"`
PageLength uint8 `json:"page_length"`
PageNumber uint8 `json:"page_number"`
Total string `json:"total"`
Orders json.RawMessage `json:"orders"`
}
// OpenOrderFinalResponse stores the unmarshalled value of OpenOrderResponse
type OpenOrderFinalResponse struct {
ErrCapture
PageLength uint8
PageNumber uint8
Total string
Orders []OrderResponse
}
// ExchangeRateResponse stores information about USD-RMB rate
type ExchangeRateResponse struct {
USD2CNY string `json:"USD2CNY"`
}
// WithdrawConfigResponse stores info about withdrawal configurations
type WithdrawConfigResponse struct {
AssetCode string `json:"assetCode"`
Minimum string `json:"min"`
CanWithDraw bool `json:"canWithDraw"`
Fee string `json:"fee"`
}
// WithdrawResponse stores info about the withdrawal
type WithdrawResponse struct {
ErrCapture `json:",omitempty"`
WithdrawID string `json:"withdrawId"`
Fee float64 `json:"fee"`
}
// RevokeWithdrawResponse stores info about the revoked withdrawal
type RevokeWithdrawResponse struct {
ErrCapture `json:",omitempty"`
WithdrawID string `json:"string"`
}
// ListDataResponse contains some of withdrawal data
type ListDataResponse struct {
ErrCapture `json:",omitempty"`
Amount float64 `json:"amount"`
AssetCode string `json:"assetCode"`
Address string `json:"address"`
Fee float64 `json:"fee"`
ID int64 `json:"id"`
Time int64 `json:"time"`
TXHash string `json:"txhash"`
Status string `json:"status"`
}
// WithdrawalResponse stores data for withdrawals
type WithdrawalResponse struct {
ErrCapture `json:",omitempty"`
TotalPages int64 `json:"totalPages"`
PageSize int64 `json:"pageSize"`
PageNo int64 `json:"pageNo"`
List []ListDataResponse `json:"list"`
}
// ErrCapture helps with error info
type ErrCapture struct {
Error int64 `json:"error_code"`
Result bool `json:"result,string"`
}
// GetAllOpenIDResp stores orderIds and currency pairs for open orders
type GetAllOpenIDResp struct {
CurrencyPair string
OrderID string
}
var errorCodes = map[int64]string{
10000: "Internal error",
10001: "The required parameters can not be empty",
10002: "Validation Failed",
10003: "Invalid parameter",
10004: "Request too frequent",
10005: "Secret key does not exist",
10006: "User does not exist",
10007: "Invalid signature",
10008: "Invalid Trading Pair",
10009: "Price and/or Amount are required for limit order",
10010: "Price and/or Amount must be more than 0",
10013: "The amount is too small",
10014: "Insufficient amount of money in account",
10015: "Invalid order type",
10016: "Insufficient account balance",
10017: "Server Error",
10018: "Page size should be between 1 and 50",
10019: "Cancel NO more than 3 orders in one request",
10020: "Volume < 0.001",
10021: "Price < 0.01",
10022: "Access denied",
10023: "Market Order is not supported yet.",
10024: "User cannot trade on this pair",
10025: "Order has been filled",
10026: "Order has been cancelld",
10027: "Order is cancelling",
10028: "Wrong query time",
10029: "'from' is not in the query time",
10030: "'from' does not match the transaction type of inqury",
10100: "Has no privilege to withdraw",
10101: "Invalid fee rate to withdraw",
10102: "Too little to withdraw",
10103: "Exceed daily limitation of withdraw",
10104: "Cancel was rejected",
10105: "Request has been cancelled",
}

View File

@@ -0,0 +1,545 @@
package lbank
import (
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
// Start starts the Lbank go routine
func (l *Lbank) Start(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
l.Run()
wg.Done()
}()
}
// Run implements the Lbank wrapper
func (l *Lbank) Run() {
if l.Verbose {
log.Debugf("%s Websocket: %s. (url: %s).\n", l.GetName(), common.IsEnabled(l.Websocket.IsEnabled()), l.Websocket.GetWebsocketURL())
log.Debugf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay)
log.Debugf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs)
}
exchangeCurrencies, err := l.GetCurrencyPairs()
if err != nil {
log.Errorf("%s Failed to get available symbols.\n", l.GetName())
} else {
var newExchangeCurrencies currency.Pairs
for _, p := range exchangeCurrencies {
newExchangeCurrencies = append(newExchangeCurrencies,
currency.NewPairFromString(p))
}
err = l.UpdateCurrencies(newExchangeCurrencies, false, true)
if err != nil {
log.Errorf("%s Failed to update available currencies %s.\n", l.GetName(), err)
}
}
}
// UpdateTicker updates and returns the ticker for a currency pair
func (l *Lbank) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tickerInfo, err := l.GetTicker(exchange.FormatExchangeCurrency(l.Name, p).String())
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Last = tickerInfo.Ticker.Latest
tickerPrice.High = tickerInfo.Ticker.High
tickerPrice.Volume = tickerInfo.Ticker.Volume
tickerPrice.Low = tickerInfo.Ticker.Low
err = ticker.ProcessTicker(l.GetName(), &tickerPrice, assetType)
if err != nil {
return tickerPrice, err
}
return ticker.GetTicker(l.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (l *Lbank) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(l.GetName(), exchange.FormatExchangeCurrency(l.Name, p), assetType)
if err != nil {
return l.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns orderbook base on the currency pair
func (l *Lbank) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.Get(l.GetName(), currency, assetType)
if err != nil {
return l.UpdateOrderbook(currency, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (l *Lbank) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
a, err := l.GetMarketDepths(exchange.FormatExchangeCurrency(l.Name, p).String(), "60", "1")
if err != nil {
return orderBook, err
}
for i := range a.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.Item{
Price: a.Asks[i][0],
Amount: a.Asks[i][1]})
}
for i := range a.Bids {
orderBook.Bids = append(orderBook.Bids, orderbook.Item{
Price: a.Bids[i][0],
Amount: a.Bids[i][1]})
}
orderBook.Pair = p
orderBook.ExchangeName = l.GetName()
orderBook.AssetType = assetType
err = orderBook.Process()
if err != nil {
return orderBook, err
}
return orderbook.Get(l.Name, p, assetType)
}
// GetAccountInfo retrieves balances for all enabled currencies for the
// Lbank exchange
func (l *Lbank) GetAccountInfo() (exchange.AccountInfo, error) {
var info exchange.AccountInfo
data, err := l.GetUserInfo()
if err != nil {
return info, err
}
var account exchange.Account
for key, val := range data.Info.Asset {
c := currency.NewCode(key)
hold, ok := data.Info.Freeze[key]
if !ok {
return info, fmt.Errorf("hold data not found with %s", key)
}
totalVal, err := strconv.ParseFloat(val, 64)
if err != nil {
return info, err
}
totalHold, err := strconv.ParseFloat(hold, 64)
if err != nil {
return info, err
}
account.Currencies = append(account.Currencies,
exchange.AccountCurrencyInfo{CurrencyName: c,
TotalValue: totalVal,
Hold: totalHold})
}
info.Accounts = append(info.Accounts, account)
info.Exchange = l.GetName()
return info, nil
}
// GetFundingHistory returns funding history, deposits and
// withdrawals
func (l *Lbank) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (l *Lbank) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// SubmitOrder submits a new order
func (l *Lbank) SubmitOrder(p currency.Pair, side exchange.OrderSide, _ exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) {
var resp exchange.SubmitOrderResponse
if side != exchange.BuyOrderSide && side != exchange.SellOrderSide {
return resp, fmt.Errorf("%s orderside is not supported by the exchange", side)
}
tempResp, err := l.CreateOrder(exchange.FormatExchangeCurrency(l.Name, p).String(), side.ToString(), amount, price)
if err != nil {
return resp, err
}
resp.IsOrderPlaced = true
resp.OrderID = tempResp.OrderID
return resp, nil
}
// ModifyOrder will allow of changing orderbook placement and limit to
// market conversion
func (l *Lbank) ModifyOrder(action *exchange.ModifyOrder) (string, error) {
return "", common.ErrFunctionNotSupported
}
// CancelOrder cancels an order by its corresponding ID number
func (l *Lbank) CancelOrder(order *exchange.OrderCancellation) error {
_, err := l.RemoveOrder(exchange.FormatExchangeCurrency(l.Name, order.CurrencyPair).String(), order.OrderID)
return err
}
// CancelAllOrders cancels all orders associated with a currency pair
func (l *Lbank) CancelAllOrders(orders *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) {
var resp exchange.CancelAllOrdersResponse
orderIDs, err := l.getAllOpenOrderID()
if err != nil {
return resp, nil
}
for key := range orderIDs {
if key != orders.CurrencyPair.String() {
continue
}
var x, y = 0, 0
var input string
var tempSlice []string
for x <= len(orderIDs[key]) {
x++
for y != x {
tempSlice = append(tempSlice, orderIDs[key][y])
if y%3 == 0 {
input = strings.Join(tempSlice, ",")
CancelResponse, err2 := l.RemoveOrder(key, input)
if err2 != nil {
return resp, err2
}
tempStringSuccess := strings.Split(CancelResponse.Success, ",")
for k := range tempStringSuccess {
resp.OrderStatus[tempStringSuccess[k]] = "Cancelled"
}
tempStringError := strings.Split(CancelResponse.Err, ",")
for l := range tempStringError {
resp.OrderStatus[tempStringError[l]] = "Failed"
}
tempSlice = tempSlice[:0]
y++
}
y++
}
input = strings.Join(tempSlice, ",")
CancelResponse, err2 := l.RemoveOrder(key, input)
if err2 != nil {
return resp, err2
}
tempStringSuccess := strings.Split(CancelResponse.Success, ",")
for k := range tempStringSuccess {
resp.OrderStatus[tempStringSuccess[k]] = "Cancelled"
}
tempStringError := strings.Split(CancelResponse.Err, ",")
for l := range tempStringError {
resp.OrderStatus[tempStringError[l]] = "Failed"
}
tempSlice = tempSlice[:0]
}
}
return resp, nil
}
// GetOrderInfo returns information on a current open order
func (l *Lbank) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var resp exchange.OrderDetail
orderIDs, err := l.getAllOpenOrderID()
if err != nil {
return resp, err
}
for key, val := range orderIDs {
for i := range val {
if val[i] != orderID {
continue
}
tempResp, err := l.QueryOrder(key, orderID)
if err != nil {
return resp, err
}
resp.Exchange = l.GetName()
resp.CurrencyPair = currency.NewPairFromString(key)
if strings.EqualFold(tempResp.Orders[0].Type, "buy") {
resp.OrderSide = exchange.BuyOrderSide
} else {
resp.OrderSide = exchange.SellOrderSide
}
z := tempResp.Orders[0].Status
switch {
case z == -1:
resp.Status = "cancelled"
case z == 0:
resp.Status = "on trading"
case z == 1:
resp.Status = "filled partially"
case z == 2:
resp.Status = "Filled totally"
case z == 4:
resp.Status = "Cancelling"
default:
resp.Status = "Invalid Order Status"
}
resp.Price = tempResp.Orders[0].Price
resp.Amount = tempResp.Orders[0].Amount
resp.ExecutedAmount = tempResp.Orders[0].DealAmount
resp.RemainingAmount = tempResp.Orders[0].Amount - tempResp.Orders[0].DealAmount
resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{
FeeType: exchange.CryptocurrencyTradeFee,
Amount: tempResp.Orders[0].Amount,
PurchasePrice: tempResp.Orders[0].Price})
if err != nil {
resp.Fee = lbankFeeNotFound
}
}
}
return resp, nil
}
// GetDepositAddress returns a deposit address for a specified currency
func (l *Lbank) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) {
return "", common.ErrFunctionNotSupported
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
// submitted
func (l *Lbank) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) {
resp, err := l.Withdraw(withdrawRequest.Address, withdrawRequest.Currency.String(), strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64), "", withdrawRequest.Description)
return resp.WithdrawID, err
}
// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is
// submitted
func (l *Lbank) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) {
return "", common.ErrFunctionNotSupported
}
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is
// submitted
func (l *Lbank) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) {
return "", common.ErrFunctionNotSupported
}
// GetWebsocket returns a pointer to the exchange websocket
func (l *Lbank) GetWebsocket() (*wshandler.Websocket, error) {
return nil, common.ErrNotYetImplemented
}
// GetActiveOrders retrieves any orders that are active/open
func (l *Lbank) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
var finalResp []exchange.OrderDetail
var resp exchange.OrderDetail
tempData, err := l.getAllOpenOrderID()
if err != nil {
return finalResp, err
}
for key, val := range tempData {
for x := range val {
tempResp, err := l.QueryOrder(key, val[x])
if err != nil {
return finalResp, err
}
resp.Exchange = l.GetName()
resp.CurrencyPair = currency.NewPairFromString(key)
if strings.EqualFold(tempResp.Orders[0].Type, "buy") {
resp.OrderSide = exchange.BuyOrderSide
} else {
resp.OrderSide = exchange.SellOrderSide
}
z := tempResp.Orders[0].Status
switch {
case z == -1:
resp.Status = "cancelled"
case z == 1:
resp.Status = "on trading"
case z == 2:
resp.Status = "filled partially"
case z == 3:
resp.Status = "Filled totally"
case z == 4:
resp.Status = "Cancelling"
default:
resp.Status = "Invalid Order Status"
}
resp.Price = tempResp.Orders[0].Price
resp.Amount = tempResp.Orders[0].Amount
resp.OrderDate = time.Unix(tempResp.Orders[0].CreateTime, 9)
resp.ExecutedAmount = tempResp.Orders[0].DealAmount
resp.RemainingAmount = tempResp.Orders[0].Amount - tempResp.Orders[0].DealAmount
resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{
FeeType: exchange.CryptocurrencyTradeFee,
Amount: tempResp.Orders[0].Amount,
PurchasePrice: tempResp.Orders[0].Price})
if err != nil {
resp.Fee = lbankFeeNotFound
}
for y := int(0); y < len(getOrdersRequest.Currencies); y++ {
if getOrdersRequest.Currencies[y].String() != key {
continue
}
if getOrdersRequest.OrderSide == "ANY" {
finalResp = append(finalResp, resp)
continue
}
if strings.EqualFold(getOrdersRequest.OrderSide.ToString(), tempResp.Orders[0].Type) {
finalResp = append(finalResp, resp)
}
}
}
}
return finalResp, nil
}
// GetOrderHistory retrieves account order information *
// Can Limit response to specific order status
func (l *Lbank) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
var finalResp []exchange.OrderDetail
var resp exchange.OrderDetail
var tempCurr currency.Pairs
var x int
if len(getOrdersRequest.Currencies) == 0 {
tempCurr = l.GetEnabledCurrencies()
} else {
for x < len(getOrdersRequest.Currencies) {
tempCurr = getOrdersRequest.Currencies
}
}
for a := range tempCurr {
p := exchange.FormatExchangeCurrency(l.Name, tempCurr[a])
b := int64(1)
tempResp, err := l.QueryOrderHistory(exchange.FormatExchangeCurrency(l.Name, p).String(), strconv.FormatInt(b, 10), "200")
if err != nil {
return finalResp, err
}
for len(tempResp.Orders) != 0 {
tempResp, err = l.QueryOrderHistory(exchange.FormatExchangeCurrency(l.Name, p).String(), strconv.FormatInt(b, 10), "200")
if err != nil {
return finalResp, err
}
for x := 0; x < len(tempResp.Orders); x++ {
resp.Exchange = l.GetName()
resp.CurrencyPair = currency.NewPairFromString(tempResp.Orders[x].Symbol)
if strings.EqualFold(tempResp.Orders[x].Type, "buy") {
resp.OrderSide = exchange.BuyOrderSide
} else {
resp.OrderSide = exchange.SellOrderSide
}
z := tempResp.Orders[x].Status
switch {
case z == -1:
resp.Status = "cancelled"
case z == 1:
resp.Status = "on trading"
case z == 2:
resp.Status = "filled partially"
case z == 3:
resp.Status = "Filled totally"
case z == 4:
resp.Status = "Cancelling"
default:
resp.Status = "Invalid Order Status"
}
resp.Price = tempResp.Orders[x].Price
resp.Amount = tempResp.Orders[x].Amount
resp.OrderDate = time.Unix(tempResp.Orders[x].CreateTime, 9)
resp.ExecutedAmount = tempResp.Orders[x].DealAmount
resp.RemainingAmount = tempResp.Orders[x].Price - tempResp.Orders[x].DealAmount
resp.Fee, err = l.GetFeeByType(&exchange.FeeBuilder{
FeeType: exchange.CryptocurrencyTradeFee,
Amount: tempResp.Orders[x].Amount,
PurchasePrice: tempResp.Orders[x].Price})
if err != nil {
resp.Fee = lbankFeeNotFound
}
finalResp = append(finalResp, resp)
b++
}
}
}
return finalResp, nil
}
// GetFeeByType returns an estimate of fee based on the type of transaction *
func (l *Lbank) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
var resp float64
if feeBuilder.FeeType == exchange.CryptocurrencyTradeFee {
return feeBuilder.Amount * feeBuilder.PurchasePrice * l.Fee, nil
}
if feeBuilder.FeeType == exchange.CryptocurrencyWithdrawalFee {
withdrawalFee, err := l.GetWithdrawConfig(feeBuilder.Pair.Base.Lower().String())
if err != nil {
return resp, err
}
var tempFee string
temp := strings.Split(withdrawalFee[0].Fee, ":\"")
if len(temp) > 1 {
tempFee = strings.TrimRight(temp[1], ",\"type")
} else {
tempFee = temp[0]
}
resp, err = strconv.ParseFloat(tempFee, 64)
if err != nil {
return resp, err
}
}
return resp, nil
}
// GetAllOpenOrderID returns all open orders by currency pairs
func (l *Lbank) getAllOpenOrderID() (map[string][]string, error) {
allPairs := l.GetEnabledCurrencies()
resp := make(map[string][]string)
for a := range allPairs {
p := exchange.FormatExchangeCurrency(l.Name, allPairs[a])
b := int64(1)
tempResp, err := l.GetOpenOrders(exchange.FormatExchangeCurrency(l.Name, p).String(), strconv.FormatInt(b, 10), "200")
if err != nil {
return resp, err
}
tempData := len(tempResp.Orders)
for tempData != 0 {
tempResp, err = l.GetOpenOrders(exchange.FormatExchangeCurrency(l.Name, p).String(), strconv.FormatInt(b, 10), "200")
if err != nil {
return resp, err
}
if len(tempResp.Orders) == 0 {
return resp, nil
}
for c := 0; c < tempData; c++ {
resp[exchange.FormatExchangeCurrency(l.Name, p).String()] = append(resp[exchange.FormatExchangeCurrency(l.Name, p).String()], tempResp.Orders[c].OrderID)
}
tempData = len(tempResp.Orders)
b++
}
}
return resp, nil
}
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (l *Lbank) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrNotYetImplemented
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (l *Lbank) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
return common.ErrNotYetImplemented
}
// AuthenticateWebsocket authenticates it
func (l *Lbank) AuthenticateWebsocket() error {
return common.ErrNotYetImplemented
}
// GetSubscriptions gets subscriptions
func (l *Lbank) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return nil, common.ErrNotYetImplemented
}

1
testdata/README.md vendored
View File

@@ -42,3 +42,4 @@ When submitting a PR, please abide by our coding guidelines:
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***

View File

@@ -443,6 +443,49 @@
}
]
},
{
"name": "LBank",
"enabled": true,
"verbose": false,
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"httpDebugging": false,
"authenticatedApiSupport": false,
"authenticatedWebsocketApiSupport": false,
"apiKey": "Key",
"apiSecret": "Secret",
"apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"proxyAddress": "",
"websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API",
"availablePairs": "fbc_usdt,hds_usdt,galt_usdt,dxn_usdt,iog_usdt,ioex_usdt,vollar_usdt,oath_usdt,bloc_usdt,btc_lbcn,eth_lbcn,usdt_lbcn,btc_usdt,eth_usdt,eth_btc,abbc_btc,bzky_eth,onot_eth,kisc_eth,bxa_usdt,atp_usdt,mat_usdt,sky_btc,sky_lbcn,rnt_usdt,vena_usdt,grin_usdt,ida_usdt,pnt_usdt,bsv_btc,bsv_usdt,opx_usdt,tena_eth,seer_lbcn,vet_lbcn,vtho_btc,vnx_lbcn,vnx_btc,amo_eth,ubex_btc,eos_btc,ubex_usdt,tns_lbcn,tns_btc,ali_eth,sdc_eth,sait_eth,artcn_usdt,dax_btc,dax_eth,dali_usdt,vet_usdt,ten_usdt,bch_usdt,neo_usdt,qtum_usdt,zec_usdt,vet_btc,pai_btc,pnt_btc,bch_btc,ltc_btc,neo_btc,dash_btc,etc_btc,qtum_btc,zec_btc,sc_btc,bts_btc,cpx_btc,xwc_btc,fil6_btc,fil12_btc,fil36_btc,eos_usdt,ut_eth,ela_eth,vet_eth,vtho_eth,pai_eth,bfdt_eth,her_eth,ptt_eth,tac_eth,idhub_eth,ssc_eth,skm_eth,iic_eth,ply_eth,ext_eth,eos_eth,yoyow_eth,trx_eth,qtum_eth,zec_eth,bts_eth,btm_eth,mith_eth,nas_eth,man_eth,dbc_eth,bto_eth,ddd_eth,cpx_eth,cs_eth,iht_eth,tky_eth,ocn_eth,dct_eth,zpt_eth,eko_eth,mda_eth,pst_eth,xwc_eth,put_eth,pnt_eth,aac_eth,fil6_eth,fil12_eth,fil36_eth,uip_eth,seer_eth,bsb_eth,cdc_eth,grams_eth,ddmx_eth,eai_eth,inc_eth,bnb_usdt,ht_usdt,bot_eth,kbc_btc,kbc_usdt,mai_usdt,phv_usdt,hnb_usdt,gt_usdt,b91_usdt,voken_usdt,cye_usdt,brc_usdt,btc_ausd",
"enabledPairs": "eth_btc",
"baseCurrencies": "USD",
"assetTypes": "SPOT",
"supportsAutoPairUpdates": true,
"configCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
},
"requestCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
},
"bankAccounts": [
{
"bankName": "",
"bankAddress": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
},
{
"name": "Bittrex",
"enabled": true,

View File

@@ -28,9 +28,6 @@ const (
currencyFXCurrencylayerPath = "..%s..%scurrency%sforexprovider%scurrencylayer%s"
currencyFXFixerPath = "..%s..%scurrency%sforexprovider%sfixer.io%s"
currencyFXOpenExchangeRatesPath = "..%s..%scurrency%sforexprovider%sopenexchangerates%s"
currencyPairPath = "..%s..%scurrency%spair%s"
currencySymbolPath = "..%s..%scurrency%ssymbol%s"
currencyTranslationPath = "..%s..%scurrency%stranslation%s"
eventsPath = "..%s..%sevents%s"
exchangesPath = "..%s..%sexchanges%s"
exchangesNoncePath = "..%s..%sexchanges%snonce%s"
@@ -67,6 +64,7 @@ const (
itbit = "..%s..%sexchanges%sitbit%s"
kraken = "..%s..%sexchanges%skraken%s"
lakebtc = "..%s..%sexchanges%slakebtc%s"
lbank = "..%s..%sexchanges%slbank%s"
localbitcoins = "..%s..%sexchanges%slocalbitcoins%s"
okcoin = "..%s..%sexchanges%sokcoin%s"
okex = "..%s..%sexchanges%sokex%s"
@@ -198,9 +196,6 @@ func addPaths() {
codebasePaths["currency forexprovider currencylayer"] = fmt.Sprintf(currencyFXCurrencylayerPath, path, path, path, path, path)
codebasePaths["currency forexprovider fixer"] = fmt.Sprintf(currencyFXFixerPath, path, path, path, path, path)
codebasePaths["currency forexprovider openexchangerates"] = fmt.Sprintf(currencyFXOpenExchangeRatesPath, path, path, path, path, path)
codebasePaths["currency pair"] = fmt.Sprintf(currencyPairPath, path, path, path, path)
codebasePaths["currency symbol"] = fmt.Sprintf(currencySymbolPath, path, path, path, path)
codebasePaths["currency translation"] = fmt.Sprintf(currencyTranslationPath, path, path, path, path)
codebasePaths["events"] = fmt.Sprintf(eventsPath, path, path, path)
@@ -239,6 +234,7 @@ func addPaths() {
codebasePaths["exchanges itbit"] = fmt.Sprintf(itbit, path, path, path, path)
codebasePaths["exchanges kraken"] = fmt.Sprintf(kraken, path, path, path, path)
codebasePaths["exchanges lakebtc"] = fmt.Sprintf(lakebtc, path, path, path, path)
codebasePaths["exchanges lbank"] = fmt.Sprintf(lbank, path, path, path, path)
codebasePaths["exchanges localbitcoins"] = fmt.Sprintf(localbitcoins, path, path, path, path)
codebasePaths["exchanges okcoin"] = fmt.Sprintf(okcoin, path, path, path, path)
codebasePaths["exchanges okex"] = fmt.Sprintf(okex, path, path, path, path)

View File

@@ -0,0 +1,98 @@
{{define "exchanges lbank" -}}
{{template "header" .}}
## Lbank Exchange
### Current Features
+ REST Support
### How to enable
+ [Enable via configuration](https://githul.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 l exchange.IBotExchange
for i := range bot.exchanges {
if bot.exchanges[i].GetName() == "Lbank" {
l = bot.exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := l.GetTickerPrice()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := l.GetOrderbookEx()
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 := l.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := l.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := l.GetOrderBook()
if err != nil {
// Handle error
}
// Private calls - make sure your APIKEY and APISECRET are set and
// AuthenticatedAPISupport is set to true
// GetUserInfo returns account info
accountInfo, err := l.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := l.Trade(...)
if err != nil {
// Handle error
}
```
### Please click GoDocs chevron above to view current GoDoc information for this package
{{template "contributions"}}
{{template "donations"}}
{{end}}

View File

@@ -40,6 +40,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Huobi.Hadax | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| Lbank | Yes | No | NA |
| LakeBTC | Yes | No | NA |
| LocalBitcoins | Yes | NA | NA |
| OKCoin International | Yes | Yes | No |