mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 23:16:49 +00:00
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:
14
CONTRIBUTORS
14
CONTRIBUTORS
@@ -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
|
||||
|
||||
17
README.md
17
README.md
@@ -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 |
|
||||
@@ -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,...",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
133
exchanges/lbank/README.md
Normal 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">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-corp/gocryptotrader)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/lbank)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](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
622
exchanges/lbank/lbank.go
Normal 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)
|
||||
}
|
||||
387
exchanges/lbank/lbank_test.go
Normal file
387
exchanges/lbank/lbank_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
270
exchanges/lbank/lbank_types.go
Normal file
270
exchanges/lbank/lbank_types.go
Normal 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",
|
||||
}
|
||||
545
exchanges/lbank/lbank_wrapper.go
Normal file
545
exchanges/lbank/lbank_wrapper.go
Normal 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
1
testdata/README.md
vendored
@@ -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***
|
||||
|
||||
|
||||
43
testdata/configtest.json
vendored
43
testdata/configtest.json
vendored
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
98
tools/documentation/exchanges_templates/lbank.tmpl
Normal file
98
tools/documentation/exchanges_templates/lbank.tmpl
Normal 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}}
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user