Beta 7 with some repo names changed.

This commit is contained in:
Luis Fernando Molina committed 2020-11-15 13:05:19 +01:00
1 parent 0f7b40cca7
commit 963c19493a
3408 files changed
+462217 -15516

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
'use strict';
module.exports = {
"env": {
"node": true,
"mocha": true
},
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "script",
},
"extends": ["eslint:recommended"],
"rules": {
"semi": ["error", "never"],
"no-unused-vars": ["off"],
"quotes": ["off", "single"],
"func-call-spacing": ["error", "always"],
"one-var": "off",
"indent": ["error", 4],
"comma-style": "off",
"no-multi-spaces": "off",
"comma-dangle": "off",
"spaced-comment": "off",
"camelcase": "off",
"padded-blocks": "off",
"multiline-comment-style": "off",
"curly": "off",
"rest-spread-spacing": "off",
"no-multiple-empty-lines": "off",
"no-undef-init": "off",
"no-useless-return": "off",
"no-console": "off",
"operator-linebreak": "off",
"key-spacing": "off",
"brace-style": "off",
"padding-line-between-statements": ["off",
{ "blankLine": "always", "prev":"function", "next": "*" },
{ "blankLine": "always", "prev":"directive", "next": "*" },
{ "blankLine": "always", "prev":"*", "next": "cjs-export" },
],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
},
}
+61
View File
@@ -0,0 +1,61 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, chai = require ('chai')
, assert = chai.assert
// ----------------------------------------------------------------------------
module.exports = (exchange, balance, method) => {
let currencies = [
'USD',
'CNY',
'EUR',
'BTC',
'ETH',
'JPY',
'LTC',
'DASH',
'DOGE',
'UAH',
'RUB',
'XRP',
]
// log.yellow (balance)
assert (typeof balance['total'] === 'object')
assert (typeof balance['free'] === 'object')
assert (typeof balance['used'] === 'object')
for (let currency of Object.keys (balance['total'])) {
let total = balance['total'][currency]
let free = balance['free'][currency]
let used = balance['used'][currency]
if (total !== undefined && free !== undefined && used !== undefined) {
assert (total === free + used, 'free and used do not sum to total ' + exchange.id)
}
}
let result = currencies
.filter (currency => (currency in balance) &&
(balance[currency]['total'] !== undefined))
if (result.length > 0) {
result = result.map (currency => currency + ': ' + balance[currency]['total'])
if (exchange.currencies.length > result.length)
result = result.join (', ') + ' + more...'
else
result = result.join (', ')
} else {
result = 'zero balance'
}
log (result)
}
+55
View File
@@ -0,0 +1,55 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, currency, method) => {
const format = {
'id': 'btc', // string literal for referencing within an exchange
'code': 'BTC', // uppercase string literal of a pair of currencies
//----------------------------------------------------------------------
// commented temporarily to bring currencies to consistency first
// 'name': 'Bitcoin', // uppercase string, base currency, 3 or more letters
// 'quote': 'USD', // uppercase string, quote currency, 3 or more letters
// 'withdraw': true, // can withdraw
// 'deposit': true, // can deposit
// 'active': true, // can both withdraw & deposit
// 'precision': 8, // number of decimal digits "after the dot"
// 'limits': { // value limits when placing orders on this market
// 'amount': {
// 'min': 0.01, // order amount should be > min
// 'max': 1000, // order amount should be < max
// },
// 'price': {
// 'min': 0.01, // order price should be > min
// 'max': 1000, // order price should be < max
// },
// 'cost': { // order cost = price * amount
// 'min': 0.01, // order cost should be > min
// 'max': 1000, // order cost should be < max
// },
// },
// 'info': {}, // the original unparsed market info from the exchange
//----------------------------------------------------------------------
}
expect (currency).to.deep.include.all.keys (format)
// expect (currency['precision']).to.not.be.undefined
// expect (currency['limits']['amount']['min']).to.not.be.undefined
// expect (currency['limits']['price']['min']).to.not.be.undefined
// expect (market['limits']['cost']['min']).to.not.be.undefined
// log (market)
return currency
}
+24
View File
@@ -0,0 +1,24 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, testBalance = require ('./test.balance.js')
// ----------------------------------------------------------------------------
module.exports = async (exchange) => {
if (!(exchange.has.fetchBalance)) {
log (exchange.id.green, ' does not have fetchBalance')
return
}
log ('fetching balance...')
const response = await exchange.fetchBalance ()
testBalance (exchange, response)
return response
}
+40
View File
@@ -0,0 +1,40 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testOrder = require ('./test.order.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
if (exchange.has.fetchClosedOrders) {
// log ('fetching closed orders...')
let orders = await exchange.fetchClosedOrders (symbol)
log ('fetched', orders.length.toString ().green, 'closed orders, testing each')
assert (orders instanceof Array)
let now = Date.now ()
for (let i = 0; i < orders.length; i++) {
let order = orders[i]
testOrder (exchange, order, symbol, now)
assert (order.status === 'closed' || order.status === 'canceled')
}
// log (asTable (orders))
} else {
log ('fetching closed orders not supported')
}
}
+38
View File
@@ -0,0 +1,38 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testCurrency = require ('./test.currency.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange) => {
const skippedExchanges = [
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchCurrencies...')
return
}
if (exchange.has.fetchCurrencies) {
// log ('fetching currencies...')
const method = 'fetchCurrencies'
const currencies = await exchange[method] ()
Object.values (currencies).forEach (currency => testCurrency (exchange, currency, method))
return currencies
} else {
log ('fetching currencies not supported')
}
}
+39
View File
@@ -0,0 +1,39 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testTransaction = require ('./test.transaction.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, code) => {
if (exchange.has.fetchDeposits) {
// log ('fetching deposits...')
let transactions = await exchange.fetchDeposits (code)
log ('fetched', transactions.length.toString ().green, 'deposits, asserting each...')
assert (transactions instanceof Array)
let now = Date.now ()
for (let i = 0; i < transactions.length; i++) {
let transaction = transactions[i]
testTransaction (exchange, transaction, code, now)
}
// log (asTable (transactions))
} else {
log ('fetching deposits not supported')
}
}
+38
View File
@@ -0,0 +1,38 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = async (exchange) => {
const skippedExchanges = [
'bibox', // fetchFundingFees should be rewritten to fetchFundingFee
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchFundingFees...')
return
}
if (exchange.has.fetchFundingFees) {
// log ('fetching funding fees...')
const method = 'fetchFundingFees'
const fees = await exchange[method] ()
log.green (fees)
return fees
} else {
log ('fetching funding fees not supported')
}
}
+31
View File
@@ -0,0 +1,31 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testOrderBook = require ('./test.orderbook.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
// log (symbol.green, 'fetching order book...')
const method = 'fetchL2OrderBook'
if (exchange.has[method]) {
let orderbook = await exchange[method] (symbol)
testOrderBook (exchange, orderbook, method, symbol)
return orderbook
} else {
log (method + '() not supported')
}
}
+47
View File
@@ -0,0 +1,47 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testLedgerItem = require ('./test.ledgerItem')
require ('ansicolor').nice
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, code) => {
if (exchange.has.fetchLedger) {
let items = await exchange.fetchLedger (code)
assert (items instanceof Array)
log ('fetched', items.length.toString ().green, 'ledger items')
let now = Date.now ()
for (let i = 0; i < items.length; i++) {
testLedgerItem (exchange, items[i], code, now)
if (i > 0) {
assert (items[i].timestamp >= items[i - 1].timestamp)
}
}
if (exchange.has.fetchLedgerItem) {
let { id } = items.pop ()
let item = await exchange.fetchLedgerItem (id)
if (Array.isArray (item)) {
item = item[0]
}
testLedgerItem (exchange, item, code, now)
}
} else {
log ('fetching ledger items not supported')
}
}
+39
View File
@@ -0,0 +1,39 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testMarket = require ('./test.market.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange) => {
const skippedExchanges = [
'bitforex',
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchMarkets...')
return
}
if (exchange.has.fetchMarkets) {
// log ('fetching markets...')
const method = 'fetchMarkets'
const markets = await exchange[method] ()
Object.values (markets).forEach (market => testMarket (exchange, market, method))
return markets
} else {
log ('fetching markets not supported')
}
}
+53
View File
@@ -0,0 +1,53 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testTrade = require ('./test.trade.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
const skippedExchanges = [
'bitso',
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchMyTrades...')
return
}
if (exchange.has.fetchMyTrades) {
// log ('fetching my trades...')
const since = exchange.milliseconds () - 60 * 60 * 1000
let trades = await exchange.fetchMyTrades (symbol, since)
assert (trades instanceof Array)
log ('fetched', trades.length.toString ().green, 'trades')
let now = Date.now ()
for (let i = 0; i < trades.length; i++) {
testTrade (exchange, trades[i], symbol, now)
if (i > 0) {
assert (trades[i].timestamp >= trades[i - 1].timestamp)
}
}
// trades.forEach (trade => log.dim ('-'.repeat (80), "\n", trade))
// log (asTable (trades))
} else {
log ('fetching my trades not supported')
}
}
+54
View File
@@ -0,0 +1,54 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testOHLCV = require ('./test.ohlcv.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
const skippedExchanges = [
'btcalpha', // issue with 404 on a documented endpoint https://travis-ci.org/ccxt/ccxt/builds/643930431#L2213
'bitmex', // an issue with null values,to be resolved later
'cex',
'mandala',
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchOHLCV...')
return
}
if (exchange.has.fetchOHLCV) {
const timeframe = Object.keys (exchange.timeframes || { '1d': '1d' })[0]
const limit = 10
const duration = exchange.parseTimeframe (timeframe)
const since = exchange.milliseconds () - duration * limit * 1000 - 1000
// log (symbol.green, 'fetching OHLCV...')
const ohlcvs = await exchange.fetchOHLCV (symbol, timeframe, since, limit)
const now = Date.now ()
for (let i = 0; i < ohlcvs.length; i++) {
const ohlcv = ohlcvs[i]
testOHLCV (exchange, ohlcv, symbol, now)
}
log (symbol.green, 'fetched', Object.keys (ohlcvs).length.toString ().green, 'OHLCVs')
return ohlcvs
} else {
log ('fetching OHLCV not supported')
}
}
+40
View File
@@ -0,0 +1,40 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testOrder = require ('./test.order.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
if (exchange.has.fetchOpenOrders) {
// log ('fetching open orders...')
let orders = await exchange.fetchOpenOrders (symbol)
assert (orders instanceof Array)
log ('fetched', orders.length.toString ().green, 'open orders')
let now = Date.now ()
for (let i = 0; i < orders.length; i++) {
let order = orders[i]
testOrder (exchange, order, symbol, now)
assert (order.status === 'open')
}
// log (asTable (orders))
} else {
log ('fetching open orders not supported')
}
}
+32
View File
@@ -0,0 +1,32 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testOrderBook = require ('./test.orderbook.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
// log (symbol.green, 'fetching order book...')
const method = 'fetchOrderBook'
if (exchange.has[method]) {
let orderbook = await exchange[method] (symbol)
testOrderBook (exchange, orderbook, method, symbol)
return orderbook
} else {
log (method + '() not supported')
}
}
+45
View File
@@ -0,0 +1,45 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testOrderBook = require ('./test.orderbook.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange) => {
const randomSymbols = exchange.symbols.sort (() => 0.5 - Math.random ()).slice (0, 2)
const customExchangeParams = ([
'yobit',
'tidex',
'ccex',
'liqui',
]).reduce ((params, id) => ({ ...params, [id]: [randomSymbols], }), {})
const args = (exchange.id in customExchangeParams) ? customExchangeParams[exchange.id] : []
const method = 'fetchOrderBooks'
if (exchange.has[method]) {
// log ('fetching order books...')
let orderbooks = await exchange[method] (...args)
// log.green (orderbooks)
Object.entries (orderbooks).forEach (([symbol, orderbook]) => {
testOrderBook (exchange, orderbook, method, symbol)
})
// return orderbooks
} else {
log (method + '() not supported')
}
}
+49
View File
@@ -0,0 +1,49 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testOrder = require ('./test.order.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
const skippedExchanges = [
'mandala',
'rightbtc',
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchMyTrades...')
return
}
if (exchange.has.fetchOrders) {
// log ('fetching orders...')
const orders = await exchange.fetchOrders (symbol)
log ('fetched', orders.length.toString ().green, 'orders, asserting each...')
assert (orders instanceof Array)
const now = Date.now ()
for (let i = 0; i < orders.length; i++) {
const order = orders[i]
testOrder (exchange, order, symbol, now)
}
// log (asTable (orders))
} else {
log ('fetching orders not supported')
}
}
+37
View File
@@ -0,0 +1,37 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = async (exchange) => {
if (exchange.has.fetchStatus) {
// log ('fetching status...')
const method = 'fetchStatus'
const status = await exchange[method] ()
const sampleStatus = {
'status': 'ok', // 'ok', 'shutdown', 'error', 'maintenance'
'updated': undefined, // integer, last updated timestamp in milliseconds if updated via the API
'eta': undefined, // when the maintenance or outage is expected to end
'url': undefined, // a link to a GitHub issue or to an exchange post on the subject
}
assert.containsAllKeys (status, sampleStatus)
return status
} else {
log ('fetching status not supported')
}
}
+55
View File
@@ -0,0 +1,55 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testTicker = require ('./test.ticker.js')
// ----------------------------------------------------------------------------
const printTickerOneLiner = (ticker, method, symbol) => {
log (symbol.toString ().green,
method,
ticker['datetime'],
'bid: ' + (ticker['bid']),
'ask: ' + (ticker['ask']))
}
// ----------------------------------------------------------------------------
module.exports = async (exchange, symbol) => {
const method = 'fetchTicker'
const skippedExchanges = [
'digifinex',
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping ' + method + '...')
return
}
if (exchange.has[method]) {
// log (symbol.green, 'fetching ticker...')
let ticker = await exchange.fetchTicker (symbol)
testTicker (exchange, ticker, method, symbol)
printTickerOneLiner (ticker, method, symbol)
return ticker
} else {
log (symbol.green, 'fetchTicker () not supported')
}
}
+53
View File
@@ -0,0 +1,53 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testTicker = require ('./test.ticker.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
const skippedExchanges = [
'binance',
'digifinex',
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetch all tickers...')
return
}
if (exchange.has.fetchTickers) {
// log ('fetching all tickers at once...')
const method = 'fetchTickers'
let tickers = undefined
try {
tickers = await exchange[method] ()
log ('fetched all', Object.keys (tickers).length.toString ().green, 'tickers')
} catch (e) {
log ('failed to fetch all tickers, fetching multiple tickers at once...')
tickers = await exchange[method] ([ symbol ])
log ('fetched', Object.keys (tickers).length.toString ().green, 'tickers')
}
Object.values (tickers).forEach (ticker => testTicker (exchange, ticker, method, symbol))
return tickers
} else {
log ('fetching all tickers at once not supported')
}
}
+48
View File
@@ -0,0 +1,48 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testTrade = require ('./test.trade.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
const skippedExchanges = [
// 'adara',
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchTrades...')
return
}
if (exchange.has.fetchTrades) {
// log (symbol.green, 'fetching trades...')
let trades = await exchange.fetchTrades (symbol)
assert (trades instanceof Array)
log (symbol.green, 'fetched', Object.values (trades).length.toString ().green, 'trades')
let now = Date.now ()
for (let i = 0; i < trades.length; i++) {
testTrade (exchange, trades[i], symbol, now)
if (i > 0) {
if (trades[i].timestamp && trades[i - 1].timestamp) {
assert (trades[i].timestamp >= trades[i - 1].timestamp)
}
}
}
// log (asTable (trades))
} else {
log (symbol.green, 'fetchTrades () not supported'.yellow)
}
}
+37
View File
@@ -0,0 +1,37 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = async (exchange) => {
const skippedExchanges = [
]
if (skippedExchanges.includes (exchange.id)) {
log (exchange.id, 'found in ignored exchanges, skipping fetchTradingFees...')
return
}
if (exchange.has.fetchTradingFees) {
// log ('fetching trading fees...')
const method = 'fetchTradingFees'
const fees = await exchange[method] ()
log.green ({ 'maker': fees['maker'], 'taker': fees['taker'] })
return fees
} else {
log ('fetching trading fees not supported')
}
}
+39
View File
@@ -0,0 +1,39 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testTransaction = require ('./test.transaction.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, code) => {
if (exchange.has.fetchTransactions) {
// log ('fetching transactions...')
let transactions = await exchange.fetchTransactions (code)
log ('fetched', transactions.length.toString ().green, 'transactions, asserting each...')
assert (transactions instanceof Array)
let now = Date.now ()
for (let i = 0; i < transactions.length; i++) {
let transaction = transactions[i]
testTransaction (exchange, transaction, code, now)
}
// log (asTable (transactions))
} else {
log ('fetching transactions not supported')
}
}
+39
View File
@@ -0,0 +1,39 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
, testTransaction = require ('./test.transaction.js')
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, code) => {
if (exchange.has.fetchWithdrawals) {
// log ('fetching deposits...')
let transactions = await exchange.fetchWithdrawals (code)
log ('fetched', transactions.length.toString ().green, 'withdrawals, asserting each...')
assert (transactions instanceof Array)
let now = Date.now ()
for (let i = 0; i < transactions.length; i++) {
let transaction = transactions[i]
testTransaction (exchange, transaction, code, now)
}
// log (asTable (transactions))
} else {
log ('fetching withdrawals not supported')
}
}
+50
View File
@@ -0,0 +1,50 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, item, code, now) => {
expect (item).to.be.an ('object')
expect (item).to.have.property ('id')
expect (typeof item.id).to.be.oneOf (['string', 'undefined'])
expect (item).to.have.property ('direction')
expect (item.direction).to.be.oneOf (['in', 'out'])
expect (item).to.have.property ('account')
expect (typeof item.account).to.be.oneOf (['string', 'undefined'])
expect (item).to.have.property ('referenceId')
expect (typeof item.referenceId).to.be.oneOf (['string', 'undefined'])
expect (item).to.have.property ('referenceAccount')
expect (typeof item.referenceAccount).to.be.oneOf (['string', 'undefined'])
expect (item).to.have.property ('type')
// expect (item.type).to.be.oneOf (['trade', 'transaction', 'margin', 'cashback', 'referral', 'transfer', 'fee', /* TODO: add more types here */ ])
expect (item).to.have.property ('currency')
expect (exchange.currencies).to.have.property (item.currency)
expect (item).to.have.property ('amount')
expect (typeof item.amount).to.be.oneOf (['number', 'undefined'])
expect (item).to.have.property ('before')
expect (typeof item.balanceBefore).to.be.oneOf (['number', 'undefined'])
expect (item).to.have.property ('after')
expect (typeof item.balanceAfter).to.be.oneOf (['number', 'undefined'])
expect (item).to.have.property ('timestamp')
expect (typeof item.timestamp).to.be.oneOf (['number', 'undefined'])
expect (item.timestamp).to.be.gt (1230940800000)
expect (item.timestamp).to.be.lt (now)
expect (item).to.have.property ('datetime')
expect (item.datetime).to.be.equal (exchange.iso8601 (item.timestamp))
expect (item).to.have.property ('fee')
if (item.fee !== undefined) {
expect (item.fee).to.be.an ('object')
expect (item.fee).to.have.property ('cost')
expect (typeof item.fee.cost).to.be.oneOf (['number', 'undefined'])
expect (item.fee).to.have.property ('currency')
expect (item.fee.currency).to.be.equal (item.currency)
}
expect (item).to.have.property ('info').that.is.an ('object')
}
+59
View File
@@ -0,0 +1,59 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, market, method) => {
const format = {
'id': 'btcusd', // string literal for referencing within an exchange
'symbol': 'BTC/USD', // uppercase string literal of a pair of currencies
'base': 'BTC', // uppercase string, base currency, 3 or more letters
'quote': 'USD', // uppercase string, quote currency, 3 or more letters
//---------------------------------------------------------------------
// commented temporarily to bring markets to consistency first
// 'active': true, // boolean, market status
// 'precision': { // number of decimal digits "after the dot"
// 'price': 8, // integer
// 'amount': 8, // integer
// 'cost': 8, // integer
// },
// 'limits': { // value limits when placing orders on this market
// 'amount': {
// 'min': 0.01, // order amount should be > min
// 'max': 1000, // order amount should be < max
// },
// 'price': {
// 'min': 0.01, // order price should be > min
// 'max': 1000, // order price should be < max
// },
// 'cost': { // order cost = price * amount
// 'min': 0.01, // order cost should be > min
// 'max': 1000, // order cost should be < max
// },
// },
// 'info': {}, // the original unparsed market info from the exchange
//---------------------------------------------------------------------
}
expect (market).to.deep.include.all.keys (format)
expect (market).to.not.have.key ('lot')
// expect (market['precision']['amount']).to.not.be.undefined
// expect (market['precision']['price']).to.not.be.undefined
// expect (market['limits']['amount']['min']).to.not.be.undefined
// expect (market['limits']['price']['min']).to.not.be.undefined
// expect (market['limits']['cost']['min']).to.not.be.undefined
// log (market)
}
+27
View File
@@ -0,0 +1,27 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, ohlcv, symbol, now) => {
assert.isOk (ohlcv)
assert (Array.isArray (ohlcv))
assert (ohlcv.length >= 6)
for (let i = 0; i < ohlcv.length; i++) {
assert (ohlcv[i] === undefined || typeof ohlcv[i] === 'number')
}
assert (ohlcv[0] > 1230940800000) // 03 Jan 2009 - first block
assert (ohlcv[0] < 2147483648000) // 19 Jan 2038 - int32 overflows
assert (ohlcv[1] === undefined || ohlcv[2] === undefined || ohlcv[1] <= ohlcv[2], 'open > high! ' + (ohlcv[1].toString () + ' > ' + ohlcv[2].toString ())) // open <= high
assert (ohlcv[3] === undefined || ohlcv[2] === undefined || ohlcv[3] <= ohlcv[2], 'low > high! ' + (ohlcv[3].toString () + ' > ' + ohlcv[2].toString ())) // low <= high
assert (ohlcv[3] === undefined || ohlcv[4] === undefined || ohlcv[3] <= ohlcv[4], 'low > close! ' + (ohlcv[3].toString () + ' > ' + ohlcv[4].toString ())) // low <= close
}
+46
View File
@@ -0,0 +1,46 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, order, symbol, now) => {
assert.isOk (order)
assert (typeof order.id === 'string')
assert (typeof order.timestamp === 'number')
assert (order.timestamp > 1230940800000) // 03 Jan 2009 - first block
assert (order.timestamp < now)
assert ('lastTradeTimestamp' in order)
assert (order.datetime === exchange.iso8601 (order.timestamp))
assert (order.status === 'open' || order.status === 'closed' || order.status === 'canceled')
assert (order.symbol === symbol)
assert (typeof order.type === 'string')
assert (order.side === 'buy' || order.side === 'sell')
assert (typeof order.price === 'number')
assert (order.price > 0)
assert (typeof order.amount === 'number')
assert (order.amount >= 0)
if (order.filled) {
assert (typeof order.filled === 'number')
assert (order.filled >= 0 && order.filled <= order.amount)
}
if (order.remaining) {
assert (typeof order.remaining === 'number')
assert (order.remaining >= 0 && order.remaining <= order.amount)
}
if (order.trades) {
assert (order.trades instanceof Array)
}
if (order.fee) {
assert (typeof order.fee.cost === 'number')
if (order.fee.cost !== 0)
assert (typeof order.fee.currency === 'string')
}
assert.isOk (order.info)
}
+74
View File
@@ -0,0 +1,74 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
// ----------------------------------------------------------------------------
const printOrderBookOneLiner = (orderbook, method, symbol) => {
const bids = orderbook.bids
const asks = orderbook.asks
log (symbol.toString ().green,
method,
orderbook['nonce'] || orderbook['datetime'],
bids.length, 'bids:', bids[0],
asks.length, 'asks:', asks[0])
}
/* ------------------------------------------------------------------------ */
module.exports = (exchange, orderbook, method, symbol) => {
const format = {
'bids': [],
'asks': [],
'timestamp': 1234567890,
'datetime': '2017-09-01T00:00:00',
'nonce': 134234234,
// 'info': {},
}
expect (orderbook).to.have.all.keys (format)
const bids = orderbook.bids
const asks = orderbook.asks
for (let i = 0; i < bids.length; i++) {
if (bids.length > (i + 1)) {
assert (bids[i][0] >= bids[i + 1][0])
}
assert (typeof bids[i][0] === 'number')
assert (typeof bids[i][1] === 'number')
}
for (let i = 0; i < asks.length; i++) {
if (asks.length > (i + 1)) {
assert (asks[i][0] <= asks[i + 1][0])
}
assert (typeof asks[i][0] === 'number')
assert (typeof asks[i][1] === 'number')
}
if (![
'coinmarketcap',
'xbtce',
'upbit', // an orderbook might have a 0-price ask occasionally
].includes (exchange.id)) {
if (bids.length && asks.length)
assert (bids[0][0] <= asks[0][0],
`bids[0][0]: ${bids[0][0]} (of ${bids.length}); asks[0][0]:${asks[0][0]} (of ${asks.length})`)
}
printOrderBookOneLiner (orderbook, method, symbol)
}
+96
View File
@@ -0,0 +1,96 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, ticker, method, symbol) => {
const format = {
'symbol': 'ETH/BTC',
'info': {},
'timestamp': 1234567890,
'datetime': '2017-09-01T00:00:00',
'high': 1.234, // highest price
'low': 1.234, // lowest price
'bid': 1.234, // current best bid (buy) price
'bidVolume': 1.234, // current best bid (buy) amount (may be missing or undefined)
'ask': 1.234, // current best ask (sell) price
'askVolume': 1.234, // current best ask (sell) amount (may be missing or undefined)
'vwap': 1.234, // volume weighed average price
'open': 1.234, // opening price
'close': 1.234, // price of last trade (closing price for current period)
'last': 1.234, // same as `close`, duplicated for convenience
'previousClose': 1.234, // closing price for the previous period
'change': 1.234, // absolute change, `last - open`
'percentage': 1.234, // relative change, `(change/open) * 100`
'average': 1.234, // average price, `(last + open) / 2`
'baseVolume': 1.234, // volume of base currency
'quoteVolume': 1.234, // volume of quote currency
}
expect (ticker).to.have.all.keys (format)
const keys = [ 'datetime', 'timestamp', 'high', 'low', 'bid', 'ask', 'baseVolume', 'quoteVolume', 'vwap' ]
assert (!('first' in ticker), '`first` field leftover in ' + exchange.id)
assert (ticker['last'] === ticker['close'], '`last` != `close` in ' + exchange.id)
log (ticker['datetime'], exchange.id, method, ticker['symbol'].green, ticker['last'])
keys.forEach (key => assert (key in ticker))
const { high, low, vwap, baseVolume, quoteVolume } = ticker
// this assert breaks QuadrigaCX sometimes... still investigating
// if (vwap)
// assert (vwap >= low && vwap <= high)
/*
if (baseVolume && quoteVolume && high && low) {
assert (quoteVolume >= baseVolume * low) // this assertion breaks therock
assert (quoteVolume <= baseVolume * high)
}
*/
if (baseVolume && vwap)
assert (quoteVolume)
if (quoteVolume && vwap)
assert (baseVolume)
// log (symbol.green, 'ticker',
// ticker['datetime'],
// ... (keys.map (key =>
// key + ': ' + ticker[key])))
if (![
'coinmarketcap',
'xbtce',
'coss',
'idex',
'mandala',
'mercado',
'okex',
'southxchange', // https://user-images.githubusercontent.com/1294454/59953532-314bea80-9489-11e9-85b3-2a711ca49aa7.png
'bitmart',
].includes (exchange.id)) {
if (ticker['baseVolume'] || ticker['quoteVolume']) {
if (ticker['bid'] && ticker['ask']) {
assert (ticker['bid'] <= ticker['ask'], 'ticker bid is greater than ticker ask!')
}
}
}
return ticker
}
+92
View File
@@ -0,0 +1,92 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, trade, symbol, now) => {
assert.isOk (trade)
const sampleTrade = {
'info': { a: 1, b: 2, c: 3 }, // the original decoded JSON as is
'id': '12345-67890:09876/54321', // string trade id
'timestamp': 1502962946216, // Unix timestamp in milliseconds
'datetime': '2017-08-17 12:42:48.000', // ISO8601 datetime with milliseconds
'symbol': 'ETH/BTC', // symbol
'order': '12345-67890:09876/54321', // string order id or undefined/None/null
'type': 'limit', // order type, 'market', 'limit' or undefined/None/null
'side': 'buy', // direction of the trade, 'buy' or 'sell'
'takerOrMaker': 'taker', // string, 'taker' or 'maker'
'price': 0.06917684, // float price in quote currency
'amount': 1.5, // amount of base currency
'cost': 0.10376526, // total cost (including fees), `price * amount`
}
assert.containsAllKeys (trade, sampleTrade)
assert.hasAnyKeys (trade, [ 'fee', 'fees' ])
const feeKeys = [ 'cost', 'currency' ]
if (trade.fee !== undefined) {
assert (typeof trade.fee === 'object')
assert.containsAllKeys (trade.fee, feeKeys)
}
if (trade.fees !== undefined) {
assert (typeof trade.fees === 'object')
assert (Array.isArray (trade.fees))
trade.fees.forEach (fee => assert.containsAllKeys (fee, feeKeys))
}
assert (trade.id === undefined || typeof trade.id === 'string')
assert (typeof trade.timestamp === 'number' || trade.timestamp === undefined)
if (trade.timestamp) {
assert (trade.timestamp > 1230940800000) // 03 Jan 2009 - first block
assert (trade.timestamp < 2147483648000) // 19 Jan 2038 - int32 overflows
}
//------------------------------------------------------------------
const adjustedNow = now + 60000
const exchangesExcludedFromTimestampCheck = [
'coinbasepro',
'coinbaseprime', // ... as well, probably
'kucoin2',
]
if (trade.timestamp) {
if (!exchangesExcludedFromTimestampCheck.includes (exchange.id)) {
assert (trade.timestamp < adjustedNow, 'trade.timestamp is greater than or equal to current time: trade: ' + exchange.iso8601 (trade.timestamp) + ' now: ' + exchange.iso8601 (now))
}
}
//------------------------------------------------------------------
assert (trade.datetime === exchange.iso8601 (trade.timestamp))
const isExchangeLackingFilteringTradesBySymbol = [
'kraken', // override for kraken and possibly other exchanges as well, can't return private trades per symbol at all
].includes (exchange.id)
if (!isExchangeLackingFilteringTradesBySymbol)
assert (trade.symbol === symbol, 'trade symbol is not equal to requested symbol: trade: ' + trade.symbol + ' reqeusted: ' + symbol)
assert (trade.type === undefined || typeof trade.type === 'string')
assert (trade.side === undefined || trade.side === 'buy' || trade.side === 'sell')
assert (trade.order === undefined || typeof trade.order === 'string')
assert (typeof trade.price === 'number', 'trade.price is not a number')
assert (trade.price > 0)
assert (typeof trade.amount === 'number', 'trade.amount is not a number')
assert (trade.amount >= 0)
assert (trade.takerOrMaker === undefined || trade.takerOrMaker === 'taker' || trade.takerOrMaker === 'maker')
assert.isOk (trade.info)
}
+37
View File
@@ -0,0 +1,37 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = (exchange, transaction, code, now) => {
assert.isOk (transaction)
assert (typeof transaction.id === 'string')
assert (typeof transaction.timestamp === 'number')
assert (transaction.timestamp > 1230940800000) // 03 Jan 2009 - first block
assert (transaction.timestamp < now)
assert ('updated' in transaction)
assert ('address' in transaction)
assert ('tag' in transaction)
assert ('txid' in transaction)
assert (transaction.datetime === exchange.iso8601 (transaction.timestamp))
assert (transaction.status === 'ok' || transaction.status === 'pending' || transaction.status === 'canceled')
assert (transaction.currency === code)
assert (typeof transaction.type === 'string')
assert (transaction.type === 'deposit' || transaction.type === 'withdrawal')
assert (typeof transaction.amount === 'number')
assert (transaction.amount >= 0)
if (transaction.fee) {
assert (typeof transaction.fee.cost === 'number')
if (transaction.fee.cost !== 0) {
assert (typeof transaction.fee.currency === 'string')
}
}
assert.isOk (transaction.info)
}
+118
View File
@@ -0,0 +1,118 @@
const { hash, ecdsa, jwt } = require ('../../../base/functions/crypto')
const { encode } = require ('../../../base/functions/encode')
const Exchange = require ('../../../base/Exchange');
const assert = require ('assert')
function equals (a, b) {
// does not check if b has more properties than a
for (const prop of Object.keys (a)) {
if (a[prop] !== b[prop]) {
return false
}
}
return true
}
const exchange = new Exchange ();
// ---------------------------------------------------------------------------------------------------------------------
assert (hash (encode (''), 'sha256', 'hex') === 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
assert (hash (encode ('cheese'), 'sha256', 'hex') === '873ac9ffea4dd04fa719e8920cd6938f0c23cd678af330939cff53c3d2855f34');
assert (hash (encode (''), 'md5', 'hex') === 'd41d8cd98f00b204e9800998ecf8427e');
assert (hash (encode ('sexyfish'), 'md5', 'hex') === 'c8a35464aa9d5683585786f44d5889f8');
assert (hash (encode (''), 'sha1', 'hex') === 'da39a3ee5e6b4b0d3255bfef95601890afd80709');
assert (hash (encode ('nutella'), 'sha1', 'hex') === 'b3d60a34b744159793c483b067c56d8affc5111a');
// ---------------------------------------------------------------------------------------------------------------------
const privateKey = '1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a';
assert (equals (ecdsa ('1a', privateKey, 'p256', 'sha256'), {
'r': '3f4537ef9e72240cdefaea19d68fd483b5e10227e89747c58a3b543bfaf9cc46',
's': '278d65adebd8d52dac34820c02cee0a0e3348f9aee91a21b8e9bfa0a23b40001',
'v': 1
}));
assert (equals (ecdsa (privateKey, privateKey, 'p256', undefined), {
'r': '9aadbfe7feca28a8b665731abd8fdd19f8b1be5fcdd1264d111b291a01c8dea2',
's': '5084765e08e169fbba470ed1f573c9fed8d68a9d883049054b601f66a6648912',
'v': 0,
}));
assert (equals (ecdsa ('1a', privateKey, 'secp256k1', 'sha256'), {
'r': '23dcb2a2a3728a35eb1a35cc01743c4609550d9cceaf2083550f13a9eb135f9f',
's': '317963fcac18e4ec9f7921b97d7ea0c82a873dd6299cbfb6af016e08ef5ed667',
'v': 0,
}));
assert (equals (ecdsa (privateKey, privateKey, 'secp256k1', undefined), {
'r': 'b84a36a6fbabd5277ede578448b93d48e70b38efb5b15b1d4e2a298accf938b1',
's': '66ebfb8221cda925526e699a59cd221bb4cc84bdc563024b1802c4d9e1d8bbe9',
'v': 1,
}));
// ---------------------------------------------------------------------------------------------------------------------
assert (exchange.hashMessage (privateKey) === '0x59ea5d98c3500c3729f95cf98aa91663f498518cc401360df2912742c232207f');
assert (equals (exchange.signHash ('0x59ea5d98c3500c3729f95cf98aa91663f498518cc401360df2912742c232207f', privateKey), {
'r': '0x6f684aa41c02da83dac3039d8805ddbe79a03b1297e247c7742cab8dfc19d341',
's': '0x62473881674550563cb028ff40a7846fd53620ddf40a20cc1003b8484a109a4a',
'v': 27
}));
assert (equals (exchange.signMessage (privateKey, privateKey), {
'r': '0x6f684aa41c02da83dac3039d8805ddbe79a03b1297e247c7742cab8dfc19d341',
's': '0x62473881674550563cb028ff40a7846fd53620ddf40a20cc1003b8484a109a4a',
'v': 27
}));
// ---------------------------------------------------------------------------------------------------------------------
const pemKeyArray = [
'-----BEGIN RSA PRIVATE KEY-----',
'MIIEpAIBAAKCAQEAqUtXQXv2uSm9zPvdJTRpu5/65rBjAoHmIiowAs+u7fY0QP9O',
'+T8CZRQjvZrfPomBiccjCxDo8mm7GkmL67rs9s7VOMQFvpTTtMBeglNSEbXJ3wTt',
'7WM5gD3ZAbhfGtwWdGpcOMX0hV5/d/fiw1zQk0+AooayTryZ7HV+DQcD1sGTYYir',
'LePUGkKV2NCvKtGOQfRAtuo0ccigMxMvEhWuExCc9Fhd077OpXMc0n7OiFqA2JWN',
'jlocM9S71Srvw/lDos8a7lGaFUV8gP1CsQY/4qTju6hhsRd4lFujVr5J9FqmDbnv',
'wQ79SSu6+lT0+ToFdDHiYOorBK2ESFR7EzlyLwIDAQABAoIBAEobQMbZjNbg/sSM',
'O/HdT6tiDGKPM8gVNLgf34RbhSeFbrpFCDzy6Al3F24YLUEi0CGPmjdt34q93blU',
'GHvIB5LCV3PR2vHiFAo7ayOBdZtrCEMn1T7lAHaynBu0qW0IiovLQzNW9AKtqv7I',
'8+qw5lyVoKmEbOkqhfaMN/Fb8MJAo3yEVyhyp4EjtFhqDQ9DuHEjp1jGthnMgm+4',
'qFyELf0DXZSAN+R9IGDqWPyo78lOcPW4pgeLuoLQ6Nn/2JAEGHFt1a7zMEw5yYbR',
'XB89Bh0dheSmopBN6Heay4YuhnNKua37OlL1/nhKpLGUNE3z/UOXeiUiMeE1C2i+',
'GjK+xgECgYEA4XH63rJ3VYDHHWUGlWffFWkEpgR4BDEkZ8MyVYQoUjQA2TpCvQ2+',
'bR7aAIGXhBnRTrIBNu9m4+am0aUfi1hVmVHqt1b2vFgEb9uKuO4U62tCFpeji+Rz',
'8C8QJyu5v5OUWdQZ4EA0d7ljeoTl50g8tpcGsDhnImLt+/jvJuFt8dECgYEAwD0n',
'rhlkEjKVHxu1xwKeczxYCqqcUV9Quq8HnPh4VJQ77ljfZ57V7sAKrFDi5XfVD9jS',
'oJPwxjw6za134VobhdvCM6WNs6AJmbR8E/b5kDMbGaZn27vt+/8JfnGaWBqpDi6W',
'a5xoJOqmcMBQBK1YleZDoY4PLbk+onVKLMfGI/8CgYEAkyUIv8euGdGWpHnm5SdJ',
'tLi5vv4Vs267uzntJWG/y3+DukTLgIdy7dgAI+pxkVgkg/+syUVSW5eU9CqZPyLl',
'o8+Sqh2Jp36vTq71iSRj5RA5r3ND3K+8eFzPZzGj6AWUA1lrljFxzV7kLfiF8gH1',
'FpvWUrhNoGT/vcFJno/uabECgYAecRC5hxfLserfVDoC261PvjyK492BHUDhbxob',
'h1U2v4qGAdjOxd5Gwm5uPxjPEZzRt5oTB5pXKe5953xWWTiGh/hGyW6ZBTy/9E65',
'sqBub0lZVHqZ1zamcwqD1WWFkiM3NbVoMQpk3iuhKzMAqpqekiofiSlqKi16+GvY',
'j4IW7QKBgQCrIUckPZ6IY3ERIN1IL4TIcK2gOJcznp7fLWpC+sv35ya3OhtDXKFt',
'GjIHmwLuiUNc0iPzA9Rw89W0zIkKWmWcxM88/ithdxh3MEeNDUGSnd4hQ9SECwxB',
'Wem3eBT7I4VtFYoaTE3/bX1SKfgBdTzIRqWKSDpgBNZg/P2Tc+s11g==',
'-----END RSA PRIVATE KEY-----',
];
const pemKey = pemKeyArray.join ("\n");
assert (jwt ({'chicken': 'salad'}, encode (pemKey), 'RS256') === 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGlja2VuIjoic2FsYWQifQ.FSKD5Y6RNzkHTuHdvG3753U7QNZ-u-GUSPfP1FMjEaK0Rr_iyQTSSmHhkdYSFFnmBvrrN_l-UwKwir52WlsgmQm9HYm0kidxbj7fWwrK2E1oe0P7OjupFjv1BZxc5W69WeaHtOPWe28tiHiON1LCnax6HgfI5lcIBsESGIIBZMVeaioQn9gDVwea7JxJvAlrhDIWZowIHTIdCQocXip7g5jREWHeEIuJNug67mwnfAFxCjvTRiTd0Bw6oBwjM3FLya-RyEyWrejQOWSuC8CNWVUHISaSmEyZ7uM6wTi2m_58TaE9mQwlef32OPErPvvBpgL5pZIyQ4ymwrCIFQLBQQ');
assert (jwt ({'lil': 'xan'}, encode ('betrayed'), 'HS256') === 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsaWwiOiJ4YW4ifQ.md-oFvZagA-NXmZoRNyJOQ7zwK-PWUMmMQ_LI9ZOKaM');
// ---------------------------------------------------------------------------------------------------------------------
assert (exchange.soliditySha3 (['0x63581b9abf2b661da0ba247e0dda1b723dcff5e3', 100, 'lulwat']) === '0x1d3e95c10fc64aee6628ea59284503a4eafc6ff13c541f00753fbd2a66cea0f5');
assert (exchange.soliditySha3 ([234]) === '0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2');
assert (exchange.soliditySha3 (['234']) === '0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2');
assert (exchange.soliditySha3 (['Hello!%']) === '0x661136a4267dba9ccdf6bfddb7c00e714de936674c4bdb065a531cf1cb15c7fc');
assert (exchange.soliditySha3 (['0x407D73d8a49eeb85D32Cf465507dd71d507100c1']) === '0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b');
+62
View File
@@ -0,0 +1,62 @@
'use strict'
const ccxt = require ('../../../../ccxt');
const assert = require ('assert');
const { ROUND_UP, ROUND_DOWN } = require ('../../../base/functions/misc');
const exchange = new ccxt.Exchange ({
'id': 'regirock',
});
// ----------------------------------------------------------------------------
assert (exchange.iso8601 (514862627000) === '1986-04-26T01:23:47.000Z');
assert (exchange.iso8601 (514862627559) === '1986-04-26T01:23:47.559Z');
assert (exchange.iso8601 (514862627062) === '1986-04-26T01:23:47.062Z');
assert (exchange.iso8601 (0) === '1970-01-01T00:00:00.000Z');
assert (exchange.iso8601 (-1) === undefined);
assert (exchange.iso8601 () === undefined);
assert (exchange.iso8601 (undefined) === undefined);
assert (exchange.iso8601 ('') === undefined);
assert (exchange.iso8601 ('a') === undefined);
assert (exchange.iso8601 ({}) === undefined);
// ----------------------------------------------------------------------------
assert (exchange.parse8601 ('1986-04-26T01:23:47.000Z') === 514862627000);
assert (exchange.parse8601 ('1986-04-26T01:23:47.559Z') === 514862627559);
assert (exchange.parse8601 ('1986-04-26T01:23:47.062Z') === 514862627062);
assert (exchange.parse8601 ('1986-04-26T01:23:47.06Z') === 514862627060);
assert (exchange.parse8601 ('1986-04-26T01:23:47.6Z') === 514862627600);
assert (exchange.parse8601 ('1977-13-13T00:00:00.000Z') === undefined);
assert (exchange.parse8601 ('1986-04-26T25:71:47.000Z') === undefined);
assert (exchange.parse8601 ('3333') === undefined);
assert (exchange.parse8601 ('Sr90') === undefined);
assert (exchange.parse8601 ('') === undefined);
assert (exchange.parse8601 () === undefined);
assert (exchange.parse8601 (undefined) === undefined);
assert (exchange.parse8601 ({}) === undefined);
assert (exchange.parse8601 (33) === undefined);
// ----------------------------------------------------------------------------
assert (exchange.parseDate ('1986-04-26 00:00:00') === 514857600000);
assert (exchange.parseDate ('1986-04-26T01:23:47.000Z') === 514862627000);
assert (exchange.parseDate ('1986-13-13 00:00:00') === undefined);
assert (exchange.roundTimeframe('5m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 13:20:00'));
assert (exchange.roundTimeframe('10m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 13:20:00'));
assert (exchange.roundTimeframe('30m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 13:00:00'));
assert (exchange.roundTimeframe('1d', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 00:00:00'));
assert (exchange.roundTimeframe('5m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 13:25:00'));
assert (exchange.roundTimeframe('10m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 13:30:00'));
assert (exchange.roundTimeframe('30m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 13:30:00'));
assert (exchange.roundTimeframe('1h', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 14:00:00'));
assert (exchange.roundTimeframe('1d', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-13 00:00:00'));
+155
View File
@@ -0,0 +1,155 @@
'use strict'
/* ------------------------------------------------------------------------ */
const { deepExtend, groupBy, filterBy, omit, sum, sortBy } = require ('../../../../ccxt')
const { strictEqual: equal, deepEqual } = require ('assert')
/* ------------------------------------------------------------------------ */
it ('deepExtend() works', () => {
let count = 0
const values = [{
a: 1,
b: 2,
d: {
a: 1,
b: [],
c: { test1: 123, test2: 321 }},
f: 5,
g: 123,
i: 321,
j: [1, 2],
},
{
b: 3,
c: 5,
d: {
b: { first: 'one', second: 'two' },
c: { test2: 222 }},
e: { one: 1, two: 2 },
f: [{ 'foo': 'bar' }],
g: (void 0),
h: /abc/g,
i: null,
j: [3, 4]
}]
const extended = deepExtend (...values)
deepEqual ({
a: 1,
b: 3,
d: {
a: 1,
b: { first: 'one', second: 'two' },
c: { test1: 123, test2: 222 }
},
f: [{ 'foo': 'bar' }],
g: undefined,
c: 5,
e: { one: 1, two: 2 },
h: /abc/g,
i: null,
j: [3, 4]
}, extended)
deepEqual (deepExtend (undefined, undefined, {'foo': 'bar' }), { 'foo': 'bar' })
})
/* ------------------------------------------------------------------------ */
it ('groupBy() works', () => {
const array = [
{ 'foo': 'a' },
{ 'foo': 'b' },
{ 'foo': 'c' },
{ 'foo': 'b' },
{ 'foo': 'c' },
{ 'foo': 'c' },
]
deepEqual (groupBy (array, 'foo'), {
'a': [ { 'foo': 'a' } ],
'b': [ { 'foo': 'b' }, { 'foo': 'b' } ],
'c': [ { 'foo': 'c' }, { 'foo': 'c' }, { 'foo': 'c' } ],
})
})
/* ------------------------------------------------------------------------ */
it ('filterBy() works', () => {
const array = [
{ 'foo': 'a' },
{ 'foo': undefined },
{ 'foo': 'b' },
{ },
{ 'foo': 'a', 'bar': 'b' },
{ 'foo': 'c' },
{ 'foo': 'd' },
{ 'foo': 'b' },
{ 'foo': 'c' },
{ 'foo': 'c' },
]
deepEqual (filterBy (array, 'foo', 'a'), [
{ 'foo': 'a' },
{ 'foo': 'a', 'bar': 'b' },
])
})
/* ------------------------------------------------------------------------ */
it ('omit works', () => {
deepEqual (omit ({ }, 'foo'), {})
deepEqual (omit ({ foo: 2 }, 'foo'), { })
deepEqual (omit ({ foo: 2, bar: 3 }, 'foo'), { bar: 3 })
deepEqual (omit ({ foo: 2, bar: 3 }, ['foo']), { bar: 3 })
deepEqual (omit ({ foo: 2, bar: 3 }), { foo: 2, bar: 3 })
deepEqual (omit ({ foo: 2, bar: 3 }, 'foo', 'bar'), {})
deepEqual (omit ({ foo: 2, bar: 3 }, ['foo'], 'bar'), {})
deepEqual (omit ({ 5: 2, bar: 3 }, [5]), { bar: 3 })
deepEqual (omit ({ 5: 2, bar: 3 }, 5), { bar: 3 })
})
/* ------------------------------------------------------------------------ */
it ('sum works', () => {
equal (undefined, sum ())
equal (2, sum (2))
equal (432, sum (2,30,400))
equal (432, sum (2, undefined, [88], 30, '7', 400, null))
})
/* ------------------------------------------------------------------------ */
it ('sortBy works', () => {
const arr = [{ x: 5 }, { x: 2 }, { x: 4 }, { x: 0 },{ x: 1 },{ x: 3 }]
sortBy (arr, 'x')
deepEqual (arr
[ { x: 0 },
{ x: 1 },
{ x: 2 },
{ x: 3 },
{ x: 4 },
{ x: 5 } ])
deepEqual (sortBy (arr, 'x', true),
[ { x: 5 },
{ x: 4 },
{ x: 3 },
{ x: 2 },
{ x: 1 },
{ x: 0 } ])
deepEqual (sortBy ([], 'x'), [])
})
/* ------------------------------------------------------------------------ */
+284
View File
@@ -0,0 +1,284 @@
'use strict'
const { numberToString, decimalToPrecision, ROUND, TRUNCATE, DECIMAL_PLACES, TICK_SIZE, PAD_WITH_ZERO, SIGNIFICANT_DIGITS, toWei, fromWei } = require ('../../../../ccxt');
const assert = require ('assert');
// ----------------------------------------------------------------------------
// toWei / fromWei
assert (toWei (1, 18) === '1000000000000000000');
assert (toWei (1, 17) === '100000000000000000');
assert (toWei (1, 16) === '10000000000000000');
assert (toWei ('1', 18) === '1000000000000000000');
assert (toWei ('1', 17) === '100000000000000000');
assert (toWei ('1', 16) === '10000000000000000');
assert (toWei (0, 18) === '0');
assert (toWei (1, 0) === '1');
assert (toWei (1, 1) === '10');
assert (toWei (1.3, 18) === '1300000000000000000');
assert (toWei ('1.3', 18) === '1300000000000000000');
assert (toWei (1.999, 17) === '199900000000000000');
assert (toWei ('1.999', 17) === '199900000000000000');
assert (toWei ('0.1', 18) === '100000000000000000');
assert (toWei ('0.01', 18) === '10000000000000000');
assert (toWei ('0.001', 18) === '1000000000000000');
assert (toWei (0.1, 18) === '100000000000000000');
assert (toWei (0.01, 18) === '10000000000000000');
assert (toWei (0.001, 18) === '1000000000000000');
assert (toWei ('0.3323340739', 18) === '332334073900000000');
assert (toWei (0.3323340739, 18) === '332334073900000000');
// let us test that we get the inverse for all these test
assert (fromWei ('1000000000000000000', 18) === 1.0);
assert (fromWei ('100000000000000000', 17) === 1.0);
assert (fromWei ('10000000000000000', 16) === 1.0);
assert (fromWei (1000000000000000000, 18) === 1.0);
assert (fromWei (100000000000000000, 17) === 1.0);
assert (fromWei (10000000000000000, 16) === 1.0);
assert (fromWei ('1300000000000000000', 18) === 1.3);
assert (fromWei (1300000000000000000, 18) === 1.3);
assert (fromWei ('199900000000000000', 17) === 1.999);
assert (fromWei (199900000000000000, 17) === 1.999);
assert (fromWei ('100000000000000000', 18) === 0.1);
assert (fromWei ('10000000000000000', 18) === 0.01);
assert (fromWei ('1000000000000000', 18) === 0.001);
assert (fromWei (100000000000000000, 18) === 0.1);
assert (fromWei (10000000000000000, 18) === 0.01);
assert (fromWei (1000000000000000, 18) === 0.001);
assert (fromWei ('332334073900000000', 18) === 0.3323340739);
assert (fromWei (332334073900000000, 18) === 0.3323340739);
// ----------------------------------------------------------------------------
// numberToString
assert (numberToString (-7.8e-7) === '-0.00000078');
assert (numberToString (7.8e-7) === '0.00000078');
assert (numberToString (-17.805e-7) === '-0.0000017805');
assert (numberToString (17.805e-7) === '0.0000017805');
assert (numberToString (-7.0005e27) === '-7000500000000000000000000000');
assert (numberToString (7.0005e27) === '7000500000000000000000000000');
assert (numberToString (-7.9e27) === '-7900000000000000000000000000');
assert (numberToString (7.9e27) === '7900000000000000000000000000');
assert (numberToString (-12.345) === '-12.345');
assert (numberToString (12.345) === '12.345');
assert (numberToString (0) === '0');
assert (numberToString (7.35946e21) === '7359460000000000000000');
// the following line breaks the test
// see https://github.com/ccxt/ccxt/issues/5744
// assert (numberToString (0.00000001) === '0.00000001');
// ----------------------------------------------------------------------------
// testDecimalToPrecisionTruncationToNDigitsAfterDot
assert (decimalToPrecision ('12.3456000', TRUNCATE, 100, DECIMAL_PLACES) === '12.3456');
assert (decimalToPrecision ('12.3456', TRUNCATE, 100, DECIMAL_PLACES) === '12.3456');
assert (decimalToPrecision ('12.3456', TRUNCATE, 4, DECIMAL_PLACES) === '12.3456');
assert (decimalToPrecision ('12.3456', TRUNCATE, 3, DECIMAL_PLACES) === '12.345');
assert (decimalToPrecision ('12.3456', TRUNCATE, 2, DECIMAL_PLACES) === '12.34');
assert (decimalToPrecision ('12.3456', TRUNCATE, 1, DECIMAL_PLACES) === '12.3');
assert (decimalToPrecision ('12.3456', TRUNCATE, 0, DECIMAL_PLACES) === '12');
assert (decimalToPrecision ('0.0000001', TRUNCATE, 8, DECIMAL_PLACES) === '0.0000001');
assert (decimalToPrecision ('0.00000001', TRUNCATE, 8, DECIMAL_PLACES) === '0.00000001');
assert (decimalToPrecision ('0.000000000', TRUNCATE, 9, DECIMAL_PLACES, PAD_WITH_ZERO) === '0.000000000');
assert (decimalToPrecision ('0.000000001', TRUNCATE, 9, DECIMAL_PLACES, PAD_WITH_ZERO) === '0.000000001');
assert (decimalToPrecision ('12.3456', TRUNCATE, -1, DECIMAL_PLACES) === '10');
assert (decimalToPrecision ('123.456', TRUNCATE, -1, DECIMAL_PLACES) === '120');
assert (decimalToPrecision ('123.456', TRUNCATE, -2, DECIMAL_PLACES) === '100');
assert (decimalToPrecision ('9.99999', TRUNCATE, -1, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('99.9999', TRUNCATE, -1, DECIMAL_PLACES) === '90');
assert (decimalToPrecision ('99.9999', TRUNCATE, -2, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('0', TRUNCATE, 0, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('-0.9', TRUNCATE, 0, DECIMAL_PLACES) === '0');
// ----------------------------------------------------------------------------
// testDecimalToPrecisionTruncationToNSignificantDigits
assert (decimalToPrecision ('0.000123456700', TRUNCATE, 100, SIGNIFICANT_DIGITS) === '0.0001234567');
assert (decimalToPrecision ('0.0001234567', TRUNCATE, 100, SIGNIFICANT_DIGITS) === '0.0001234567');
assert (decimalToPrecision ('0.0001234567', TRUNCATE, 7, SIGNIFICANT_DIGITS) === '0.0001234567');
assert (decimalToPrecision ('0.000123456', TRUNCATE, 6, SIGNIFICANT_DIGITS) === '0.000123456');
assert (decimalToPrecision ('0.000123456', TRUNCATE, 5, SIGNIFICANT_DIGITS) === '0.00012345');
assert (decimalToPrecision ('0.000123456', TRUNCATE, 2, SIGNIFICANT_DIGITS) === '0.00012');
assert (decimalToPrecision ('0.000123456', TRUNCATE, 1, SIGNIFICANT_DIGITS) === '0.0001');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 10, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.0000987');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 8, SIGNIFICANT_DIGITS) === '123.00009');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 7, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.0000');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 6, SIGNIFICANT_DIGITS) === '123');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 5, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.00');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 4, SIGNIFICANT_DIGITS) === '123');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 4, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.0');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 3, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 2, SIGNIFICANT_DIGITS) === '120');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 1, SIGNIFICANT_DIGITS) === '100');
assert (decimalToPrecision ('123.0000987654', TRUNCATE, 1, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '100');
assert (decimalToPrecision ('1234', TRUNCATE, 5, SIGNIFICANT_DIGITS) === '1234');
assert (decimalToPrecision ('1234', TRUNCATE, 5, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '1234.0');
assert (decimalToPrecision ('1234', TRUNCATE, 4, SIGNIFICANT_DIGITS) === '1234');
assert (decimalToPrecision ('1234', TRUNCATE, 4, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '1234');
assert (decimalToPrecision ('1234.69', TRUNCATE, 0, SIGNIFICANT_DIGITS) === '0');
assert (decimalToPrecision ('1234.69', TRUNCATE, 0, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0');
// ----------------------------------------------------------------------------
// testDecimalToPrecisionRoundingToNDigitsAfterDot
assert (decimalToPrecision ('12.3456000', ROUND, 100, DECIMAL_PLACES) === '12.3456');
assert (decimalToPrecision ('12.3456', ROUND, 100, DECIMAL_PLACES) === '12.3456');
assert (decimalToPrecision ('12.3456', ROUND, 4, DECIMAL_PLACES) === '12.3456');
assert (decimalToPrecision ('12.3456', ROUND, 3, DECIMAL_PLACES) === '12.346');
assert (decimalToPrecision ('12.3456', ROUND, 2, DECIMAL_PLACES) === '12.35');
assert (decimalToPrecision ('12.3456', ROUND, 1, DECIMAL_PLACES) === '12.3');
assert (decimalToPrecision ('12.3456', ROUND, 0, DECIMAL_PLACES) === '12');
// a problematic case in PHP
assert (decimalToPrecision ('10000', ROUND, 6, DECIMAL_PLACES) === '10000');
assert (decimalToPrecision ('0.00003186', ROUND, 8, DECIMAL_PLACES) === '0.00003186');
assert (decimalToPrecision ('12.3456', ROUND, -1, DECIMAL_PLACES) === '10');
assert (decimalToPrecision ('123.456', ROUND, -1, DECIMAL_PLACES) === '120');
assert (decimalToPrecision ('123.456', ROUND, -2, DECIMAL_PLACES) === '100');
assert (decimalToPrecision ('9.99999', ROUND, -1, DECIMAL_PLACES) === '10');
assert (decimalToPrecision ('99.9999', ROUND, -1, DECIMAL_PLACES) === '100');
assert (decimalToPrecision ('99.9999', ROUND, -2, DECIMAL_PLACES) === '100');
assert (decimalToPrecision ('9.999', ROUND, 3, DECIMAL_PLACES) === '9.999');
assert (decimalToPrecision ('9.999', ROUND, 2, DECIMAL_PLACES) === '10');
assert (decimalToPrecision ('9.999', ROUND, 2, DECIMAL_PLACES, PAD_WITH_ZERO) === '10.00');
assert (decimalToPrecision ('99.999', ROUND, 2, DECIMAL_PLACES, PAD_WITH_ZERO) === '100.00');
assert (decimalToPrecision ('-99.999', ROUND, 2, DECIMAL_PLACES, PAD_WITH_ZERO) === '-100.00');
// ----------------------------------------------------------------------------
// testDecimalToPrecisionRoundingToNSignificantDigits
assert (decimalToPrecision ('0.000123456700', ROUND, 100, SIGNIFICANT_DIGITS) === '0.0001234567');
assert (decimalToPrecision ('0.0001234567', ROUND, 100, SIGNIFICANT_DIGITS) === '0.0001234567');
assert (decimalToPrecision ('0.0001234567', ROUND, 7, SIGNIFICANT_DIGITS) === '0.0001234567');
assert (decimalToPrecision ('0.000123456', ROUND, 6, SIGNIFICANT_DIGITS) === '0.000123456');
assert (decimalToPrecision ('0.000123456', ROUND, 5, SIGNIFICANT_DIGITS) === '0.00012346');
assert (decimalToPrecision ('0.000123456', ROUND, 4, SIGNIFICANT_DIGITS) === '0.0001235');
assert (decimalToPrecision ('0.00012', ROUND, 2, SIGNIFICANT_DIGITS) === '0.00012');
assert (decimalToPrecision ('0.0001', ROUND, 1, SIGNIFICANT_DIGITS) === '0.0001');
assert (decimalToPrecision ('123.0000987654', ROUND, 7, SIGNIFICANT_DIGITS) === '123.0001');
assert (decimalToPrecision ('123.0000987654', ROUND, 6, SIGNIFICANT_DIGITS) === '123');
assert (decimalToPrecision ('0.00098765', ROUND, 2, SIGNIFICANT_DIGITS) === '0.00099');
assert (decimalToPrecision ('0.00098765', ROUND, 2, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0.00099');
assert (decimalToPrecision ('0.00098765', ROUND, 1, SIGNIFICANT_DIGITS) === '0.001');
assert (decimalToPrecision ('0.00098765', ROUND, 10, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0.0009876500000');
assert (decimalToPrecision ('0.098765', ROUND, 1, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0.1');
assert (decimalToPrecision ('0', ROUND, 0, SIGNIFICANT_DIGITS) === '0');
assert (decimalToPrecision ('-0.123', ROUND, 0, SIGNIFICANT_DIGITS) === '0');
assert (decimalToPrecision ('0.00000044', ROUND, 5, SIGNIFICANT_DIGITS) === '0.00000044');
// ----------------------------------------------------------------------------
// testDecimalToPrecisionRoundingToTickSize
assert (decimalToPrecision ('0.000123456700', ROUND, 0.00012, TICK_SIZE) === '0.00012');
assert (decimalToPrecision ('0.0001234567', ROUND, 0.00013, TICK_SIZE) === '0.00013');
assert (decimalToPrecision ('0.0001234567', TRUNCATE, 0.00013, TICK_SIZE) === '0');
assert (decimalToPrecision ('101.000123456700', ROUND, 100, TICK_SIZE) === '100');
assert (decimalToPrecision ('0.000123456700', ROUND, 100, TICK_SIZE) === '0');
assert (decimalToPrecision ('165', TRUNCATE, 110, TICK_SIZE) === '110');
assert (decimalToPrecision ('3210', TRUNCATE, 1110, TICK_SIZE) === '2220');
assert (decimalToPrecision ('165', ROUND, 110, TICK_SIZE) === '220');
assert (decimalToPrecision ('0.000123456789', ROUND, 0.00000012, TICK_SIZE) === '0.00012348');
assert (decimalToPrecision ('0.000123456789', TRUNCATE, 0.00000012, TICK_SIZE) === '0.00012336');
assert (decimalToPrecision ('0.01', ROUND, 0.0001, TICK_SIZE, PAD_WITH_ZERO) === '0.0100');
assert (decimalToPrecision ('0.01', TRUNCATE, 0.0001, TICK_SIZE, PAD_WITH_ZERO) === '0.0100');
assert (decimalToPrecision ('-0.000123456789', ROUND, 0.00000012, TICK_SIZE) === '-0.00012348');
assert (decimalToPrecision ('-0.000123456789', TRUNCATE, 0.00000012, TICK_SIZE) === '-0.00012336');
assert (decimalToPrecision ('-165', TRUNCATE, 110, TICK_SIZE) === '-110');
assert (decimalToPrecision ('-165', ROUND, 110, TICK_SIZE) === '-220');
assert (decimalToPrecision ('-1650', TRUNCATE, 1100, TICK_SIZE) === '-1100');
assert (decimalToPrecision ('-1650', ROUND, 1100, TICK_SIZE) === '-2200');
assert (decimalToPrecision ('0.0006', TRUNCATE, 0.0001, TICK_SIZE) === '0.0006');
assert (decimalToPrecision ('-0.0006', TRUNCATE, 0.0001, TICK_SIZE) === '-0.0006');
assert (decimalToPrecision ('0.6', TRUNCATE, 0.2, TICK_SIZE) === '0.6');
assert (decimalToPrecision ('-0.6', TRUNCATE, 0.2, TICK_SIZE) === '-0.6');
assert (decimalToPrecision ('1.2', ROUND, 0.4, TICK_SIZE) === '1.2');
assert (decimalToPrecision ('-1.2', ROUND, 0.4, TICK_SIZE) === '-1.2');
assert (decimalToPrecision ('1.2', ROUND, 0.02, TICK_SIZE) === '1.2');
assert (decimalToPrecision ('-1.2', ROUND, 0.02, TICK_SIZE) === '-1.2');
assert (decimalToPrecision ('44', ROUND, 4.4, TICK_SIZE) === '44');
assert (decimalToPrecision ('-44', ROUND, 4.4, TICK_SIZE) === '-44');
assert (decimalToPrecision ('44.00000001', ROUND, 4.4, TICK_SIZE) === '44');
assert (decimalToPrecision ('-44.00000001', ROUND, 4.4, TICK_SIZE) === '-44');
// https://github.com/ccxt/ccxt/issues/6731
assert (decimalToPrecision ('20', TRUNCATE, 0.00000001, TICK_SIZE) === '20');
// ----------------------------------------------------------------------------
// testDecimalToPrecisionNegativeNumbers
assert (decimalToPrecision ('-0.123456', TRUNCATE, 5, DECIMAL_PLACES) === '-0.12345');
assert (decimalToPrecision ('-0.123456', ROUND, 5, DECIMAL_PLACES) === '-0.12346');
// ----------------------------------------------------------------------------
// decimalToPrecision: without dot / trailing dot
assert (decimalToPrecision ('123', TRUNCATE, 0) === '123');
assert (decimalToPrecision ('123', TRUNCATE, 5, DECIMAL_PLACES) === '123');
assert (decimalToPrecision ('123', TRUNCATE, 5, DECIMAL_PLACES, PAD_WITH_ZERO) === '123.00000');
assert (decimalToPrecision ('123.', TRUNCATE, 0, DECIMAL_PLACES) === '123');
assert (decimalToPrecision ('123.', TRUNCATE, 5, DECIMAL_PLACES, PAD_WITH_ZERO) === '123.00000');
assert (decimalToPrecision ('0.', TRUNCATE, 0) === '0');
assert (decimalToPrecision ('0.', TRUNCATE, 5, DECIMAL_PLACES, PAD_WITH_ZERO) === '0.00000');
// ----------------------------------------------------------------------------
// decimalToPrecision: rounding for equidistant digits
assert (decimalToPrecision ('1.44', ROUND, 1, DECIMAL_PLACES) === '1.4');
assert (decimalToPrecision ('1.45', ROUND, 1, DECIMAL_PLACES) === '1.5');
assert (decimalToPrecision ('1.45', ROUND, 0, DECIMAL_PLACES) === '1'); // not 2
// ----------------------------------------------------------------------------
// negative precision only implemented so far in python
// pretty useless for decimal applications as anything |x| < 5 === 0
// NO_PADDING and PAD_WITH_ZERO are ignored
assert (decimalToPrecision ('5', ROUND, -1, DECIMAL_PLACES) === '10');
assert (decimalToPrecision ('4.999', ROUND, -1, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('0.0431531423', ROUND, -1, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('-69.3', ROUND, -1, DECIMAL_PLACES) === '-70');
assert (decimalToPrecision ('5001', ROUND, -4, DECIMAL_PLACES) === '10000');
assert (decimalToPrecision ('4999.999', ROUND, -4, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('69.3', TRUNCATE, -2, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('-69.3', TRUNCATE, -2, DECIMAL_PLACES) === '0');
assert (decimalToPrecision ('69.3', TRUNCATE, -1, SIGNIFICANT_DIGITS) === '60');
assert (decimalToPrecision ('-69.3', TRUNCATE, -1, SIGNIFICANT_DIGITS) === '-60');
assert (decimalToPrecision ('69.3', TRUNCATE, -2, SIGNIFICANT_DIGITS) === '0');
assert (decimalToPrecision ('1602000000000000000000', TRUNCATE, 3, SIGNIFICANT_DIGITS) === '1600000000000000000000');
// ----------------------------------------------------------------------------
// testDecimalToPrecisionErrorHandling (todo)
//
// throws (() =>
// decimalToPrecision ('123456.789', TRUNCATE, -2, DECIMAL_PLACES),
// 'negative precision is not yet supported')
//
// throws (() =>
// decimalToPrecision ('foo'),
// "invalid number (contains an illegal character 'f')")
//
// throws (() =>
// decimalToPrecision ('0.01', TRUNCATE, -1, TICK_SIZE),
// "TICK_SIZE cant be used with negative numPrecisionDigits")
+71
View File
@@ -0,0 +1,71 @@
'use strict'
/* ------------------------------------------------------------------------ */
const { setTimeout_safe
, timeout
, now
, isWindows } = require ('../../../../ccxt')
const { strictEqual: equal } = require ('assert')
, approxEqual = (a, b) => equal (true, Math.abs (a - b) <= 20)
, rejectsWith = (msg, x) => x.then (() => { throw new Error ('not rejected!') })
.catch (e => { equal (e.message, msg) })
/* ------------------------------------------------------------------------ */
it ('setTimeout_safe is working', (done) => {
// TODO: make a more robust test that is not failing on certain machines under certain conditions
if (isWindows) {
// temporarily skip this test with appveyor
// todo: refix timer functions for windows properly
done ()
} else {
const start = now ()
const calls = []
const brokenSetTimeout = (done, ms) => {
calls.push ({ when: now () - start, ms_asked: ms })
return setTimeout (done, 100) // simulates a defect setTimeout implementation that sleeps wrong time (100ms always in this test)
}
// ask to sleep 250ms
setTimeout_safe (() => {
approxEqual (calls[0].ms_asked, 250)
approxEqual (calls[1].ms_asked, 150)
approxEqual (calls[2].ms_asked, 50)
done ()
}, 250, brokenSetTimeout)
}
})
/* ------------------------------------------------------------------------ */
it ('setTimeout_safe canceling is working', (done) => {
const brokenSetTimeout = (done, ms) => setTimeout (done, 100) // simulates a defect setTimeout implementation that sleeps wrong time (100ms always in this test)
const clear = setTimeout_safe (() => { throw new Error ('shouldnt happen!') }, 250, brokenSetTimeout)
setTimeout (() => { clear () }, 200)
setTimeout (() => { done () }, 400)
})
/* ------------------------------------------------------------------------ */
it ('timeout() is working', async () => {
equal ('foo', await timeout (200, new Promise (resolve => setTimeout (() => resolve ('foo'), 100))))
await rejectsWith ('foo', timeout (100, Promise.reject (new Error ('foo'))))
await rejectsWith ('timed out', timeout (100, new Promise ((resolve, reject) => setTimeout (() => reject (new Error ('foo')), 200))))
})
/* ------------------------------------------------------------------------ */
+54
View File
@@ -0,0 +1,54 @@
'use strict'
/* ------------------------------------------------------------------------ */
const { safeFloat, safeInteger, safeValue } = require ('../../../../ccxt')
const { strictEqual: equal, deepEqual } = require ('assert')
/* ------------------------------------------------------------------------ */
it ('safeFloat/safeInteger is robust', async () => {
const $default = {}
const fns = { safeFloat, safeInteger }
for (const fn of ['safeFloat', 'safeInteger']) {
equal (fns[fn] ({ 'x': false }, 'x', $default), $default)
equal (fns[fn] ({ 'x': true }, 'x', $default), $default)
equal (fns[fn] ({ 'x': [] }, 'x', $default), $default)
equal (fns[fn] ({ 'x': [0] }, 'x', $default), $default)
equal (fns[fn] ({ 'x': [1] }, 'x', $default), $default)
equal (fns[fn] ({ 'x': {} }, 'x', $default), $default)
equal (fns[fn] ({ 'x': Number.NaN }, 'x'), undefined)
equal (fns[fn] ({ 'x': Number.POSITIVE_INFINITY }, 'x'), undefined)
equal (fns[fn] ({ 'x': null }, 'x', undefined), undefined)
equal (fns[fn] ({ 'x': null }, 'x', $default), $default)
equal (fns[fn] ({ 'x': '1.0' }, 'x'), 1.0)
equal (fns[fn] ({ 'x': '-1.0' }, 'x'), -1.0)
equal (fns[fn] ({ 'x': 1.0 }, 'x'), 1.0)
equal (fns[fn] ({ 'x': 0 }, 'x'), 0)
equal (fns[fn] ({ 'x': undefined }, 'x', $default), $default)
equal (fns[fn] ({ 'x': "" }, 'x'), undefined)
equal (fns[fn] ({ 'x': "" }, 'x', 0), 0)
equal (fns[fn] ({}, 'x'), undefined)
equal (fns[fn] ({}, 'x', 0), 0)
}
equal (safeFloat ({ 'x': 1.59999999 }, 'x'), 1.59999999)
equal (safeInteger ({ 'x': 1.59999999 }, 'x'), 1)
})
/* ------------------------------------------------------------------------ */
it ('safeValue works', () => {
equal (safeValue ({}, 'foo'), undefined)
equal (safeValue ({}, 'foo', 'bar'), 'bar')
equal (safeValue ({ 'foo': 'bar' }, 'foo'), 'bar')
equal (safeValue ({ 'foo': '' }, 'foo'), '')
equal (safeValue ({ 'foo': 0 }, 'foo'), 0)
})
/* ------------------------------------------------------------------------ */
+242
View File
@@ -0,0 +1,242 @@
'use strict'
/* ------------------------------------------------------------------------ */
global.log = require ('ololog') // for easier debugging
/* ------------------------------------------------------------------------ */
const { Exchange, keys, values, unique, index, aggregate } = require ('../../../ccxt')
const { strictEqual: equal, deepEqual } = require ('assert')
/* ------------------------------------------------------------------------ */
describe ('ccxt base code', () => {
/* ------------------------------------------------------------------------ */
require ('./functions/test.generic')
require ('./functions/test.time')
require ('./functions/test.type')
// Some test files are transpiled to other languages and must be kept very simple so we put `it` call externally to them
// Some mocha reporters will swallow `assert` failures if they are outside of `it` clause so we must call `it`
it ('decimalToPrecision() works', () => {
require ('./functions/test.number')
})
it ('date&time functions work', () => {
require('./functions/test.datetime')
})
it ('cryptography code works', () => {
require('./functions/test.crypto')
})
/* ------------------------------------------------------------------------ */
it ('calculateFee() works', () => {
const price = 100.00
const amount = 10.00
const taker = 0.0025
const maker = 0.0010
const fees = { taker, maker }
const market = {
'id': 'foobar',
'symbol': 'FOO/BAR',
'base': 'FOO',
'quote': 'BAR',
'taker': taker,
'maker': maker,
'precision': {
'amount': 8,
'price': 8,
},
}
const exchange = new Exchange ({
'id': 'mock',
'markets': {
'FOO/BAR': market,
},
})
Object.keys (fees).forEach (takerOrMaker => {
const result = exchange.calculateFee (market['symbol'], 'limit', 'sell', amount, price, takerOrMaker, {})
deepEqual (result, {
'type': takerOrMaker,
'currency': 'BAR',
'rate': fees[takerOrMaker],
'cost': fees[takerOrMaker] * amount * price,
})
})
})
/* ------------------------------------------------------------------------ */
it.skip ('exchange config extension works', () => {
cost = { min: 0.001, max: 1000 }
precision = { amount: 3 }
const exchange = new binance ({
'markets': {
'ETH/BTC': { limits: { cost }, precision },
},
})
deepEqual (exchange.markets['ETH/BTC'].limits.cost, cost)
deepEqual (exchange.markets['ETH/BTC'].precision, { price: 6, amount: 3 })
deepEqual (exchange.markets['ETH/BTC'].symbol, 'ETH/BTC')
})
/* ------------------------------------------------------------------------ */
it ('aggregate() works', () => {
const bids = [
[ 789.1, 123.0 ],
[ 789.100, 123.0 ],
[ 123.0, 456.0 ],
[ 789.0, 123.0 ],
[ 789.10, 123.0 ],
]
const asks = [
[ 123.0, 456.0 ],
[ 789.0, 123.0 ],
[ 789.10, 123.0 ],
]
deepEqual (aggregate (bids.sort ()), [
[ 123.0, 456.0 ],
[ 789.0, 123.0 ],
[ 789.1, 369.0 ],
])
deepEqual (aggregate (asks.sort ()), [
[ 123.0, 456.0 ],
[ 789.0, 123.0 ],
[ 789.10, 123.0 ],
])
deepEqual (aggregate ([]), [])
})
/* ------------------------------------------------------------------------ */
it ('parseBalance() works', () => {
const exchange = new Exchange ({
'markets': {
'ETH/BTC': { 'id': 'ETH/BTC', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', }
}
})
const input = {
'ETH': { 'free': 10, 'used': 10, 'total': 20 },
'ZEC': { 'free': 0, 'used': 0, 'total': 0 },
}
const expected = {
'ETH': { 'free': 10, 'used': 10, 'total': 20 },
'ZEC': { 'free': 0, 'used': 0, 'total': 0 },
'free': {
'ETH': 10,
'ZEC': 0,
},
'used': {
'ETH': 10,
'ZEC': 0,
},
'total': {
'ETH': 20,
'ZEC': 0,
},
}
const actual = exchange.parseBalance (input)
deepEqual (actual, expected)
})
/* ------------------------------------------------------------------------ */
it ('camelCase/camel_case property conversion works', () => {
const exchange = new Exchange ({ 'id': 'mock' })
const propsSeenBefore = index (
["isNode", "empty", "keys", "values", "extend", "clone", "index", "ordered", "unique", "keysort", "indexBy", "groupBy", "filterBy", "sortBy", "flatten", "pluck", "omit", "sum", "deepExtend", "uuid", "unCamelCase", "capitalize", "isNumber", "isArray", "isObject", "isString", "isStringCoercible", "isDictionary", "hasProps", "prop", "asFloat", "asInteger", "safeFloat", "safeInteger", "safeValue", "safeString", "decimal", "toFixed", "truncate", "truncateToString", "precisionFromString", "stringToBinary", "stringToBase64", "utf16ToBase64", "base64ToBinary", "base64ToString", "binaryToString", "binaryConcat", "urlencode", "rawencode", "urlencodeBase64", "hash", "hmac", "jwt", "time", "setTimeout_safe", "sleep", "TimedOut", "timeout", "throttle", "json", "unjson", "aggregate", "is_node", "index_by", "group_by", "filter_by", "sort_by", "deep_extend", "un_camel_case", "is_number", "is_array", "is_object", "is_string", "is_string_coercible", "is_dictionary", "has_props", "as_float", "as_integer", "safe_float", "safe_integer", "safe_value", "safe_string", "to_fixed", "truncate_to_string", "precision_from_string", "string_to_binary", "string_to_base64", "utf16To_base64", "base64To_binary", "base64To_string", "binary_to_string", "binary_concat", "urlencode_base64", "set_timeout_safe", "Timed_out", "encode", "decode", "userAgents", "headers", "proxy", "origin", "iso8601", "parse8601", "milliseconds", "microseconds", "seconds", "id", "enableRateLimit", "rateLimit", "parseJsonResponse", "substituteCommonCurrencyCodes", "parseBalanceFromOpenOrders", "verbose", "debug", "journal", "userAgent", "twofa", "timeframes", "hasPublicAPI", "hasPrivateAPI", "hasCORS", "hasDeposit", "hasFetchBalance", "hasFetchClosedOrders", "hasFetchCurrencies", "hasFetchMyTrades", "hasFetchOHLCV", "hasFetchOpenOrders", "hasFetchOrder", "hasFetchOrderBook", "hasFetchOrders", "hasFetchTicker", "hasFetchTickers", "hasFetchBidsAsks", "hasFetchTrades", "hasWithdraw", "hasCreateOrder", "hasCancelOrder", "apiKey", "secret", "uid", "login", "password", "requiredCredentials", "exceptions", "balance", "orderbooks", "tickers", "fees", "orders", "trades", "currencies", "last_http_response", "last_json_response", "arrayConcat", "market_id", "market_ids", "array_concat", "implode_params", "extract_params", "fetch_balance", "fetch_free_balance", "fetch_used_balance","fetch_total_balance", "fetch_l2_order_book", "fetch_order_book", "fetch_bids_asks", "fetch_tickers", "fetch_ticker", "fetch_trades", "fetch_order", "fetch_orders", "fetch_open_orders", "fetch_closed_orders", "fetch_order_status", "fetch_markets", "load_markets", "set_markets", "parse_balance", "parse_bid_ask", "parse_bids_asks", "parse_order_book", "parse_trades", "parse_orders", "parse_ohlcv", "parse_ohlcvs", "edit_limit_buy_order", "edit_limit_sell_order", "edit_limit_order", "edit_order", "create_limit_buy_order", "create_limit_sell_order", "create_market_buy_order", "create_market_sell_order", "create_order", "calculate_fee", "common_currency_code", "price_to_precision", "amount_to_precision", "amount_to_string", "fee_to_precision", "cost_to_precision", "constructor", "getMarket", "describe", "defaults", "nonce", "encodeURIComponent", "checkRequiredCredentials", "initRestRateLimiter", "defineRestApi", "fetch", "fetch2", "request", "handleErrors", "defaultErrorHandler", "handleRestErrors", "handleRestResponse", "setMarkets", "loadMarkets", "fetchBidsAsks", "fetchTickers", "fetchOrder", "fetchOrders", "fetchOpenOrders", "fetchClosedOrders", "fetchMyTrades", "fetchCurrencies", "fetchMarkets", "fetchOrderStatus", "account", "commonCurrencyCode", "currency", "market", "marketId", "marketIds", "symbol", "extractParams", "implodeParams", "url", "parseBidAsk", "parseBidsAsks", "fetchL2OrderBook", "parseOrderBook", "parseBalance", "fetchPartialBalance", "fetchFreeBalance", "fetchUsedBalance", "fetchTotalBalance", "filterBySinceLimit", "parseTrades", "parseOrders", "filterOrdersBySymbol", "parseOHLCV", "parseOHLCVs", "editLimitBuyOrder", "editLimitSellOrder", "editLimitOrder", "editOrder", "createLimitBuyOrder", "createLimitSellOrder", "createMarketBuyOrder", "createMarketSellOrder", "costToPrecision", "priceToPrecision", "amountToPrecision", "amountToLots", "feeToPrecision", "calculateFee", "Ymd", "YmdHMS"]
)
const props = index (Object.getOwnPropertyNames (exchange).concat (
Object.getOwnPropertyNames (exchange.constructor.prototype)))
for (const k of Array.from (propsSeenBefore))
if (this[k] && !props.has (k))
throw new Error (`missing prop: ${k}`)
for (const k of Array.from (props))
if (!propsSeenBefore.has (k))
log.magenta.noLocate (`+ ${k}`)
})
/* ------------------------------------------------------------------------ */
it ('camelCase/camel_case property conversion works #2', () => {
class Derived extends Exchange {}
const derived = new Derived ()
equal (typeof derived.load_markets, 'function')
})
/* ------------------------------------------------------------------------ */
it ('legacy .hasSomething maps to has.something automatically', () => {
const exchange = new Exchange ({
id: 'mock',
has: {
CORS: true,
publicAPI: false,
fetchDepositAddress: 'emulated'
}
})
equal (exchange.hasCORS, true)
equal (exchange.hasPublicAPI, false)
equal (exchange.hasFetchDepositAddress, true)
})
/* ------------------------------------------------------------------------ */
if (Exchange.hasWeb3 ()) {
it ('getZeroExOrderHashV2 return valid signature for example order', () => {
// based on https://github.com/0xProject/0x-monorepo/blob/development/python-packages/order_utils/test/test_generate_order_hash_hex.py
const exampleOrder = {
"makerAddress": "0x0000000000000000000000000000000000000000",
"takerAddress": "0x0000000000000000000000000000000000000000",
"senderAddress": "0x0000000000000000000000000000000000000000",
"feeRecipientAddress": "0x0000000000000000000000000000000000000000",
"makerAssetData": "0x0000000000000000000000000000000000000000",
"takerAssetData": "0x0000000000000000000000000000000000000000",
"salt": "0",
"makerFee": "0",
"takerFee": "0",
"makerAssetAmount": "0",
"takerAssetAmount": "0",
"expirationTimeSeconds": "0",
"exchangeAddress": "0x0000000000000000000000000000000000000000",
}
const expectedOrderHash = '0xfaa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422';
class Derived extends Exchange {}
const derived = new Derived ()
const orderHash = derived.getZeroExOrderHashV2(exampleOrder)
equal (orderHash, expectedOrderHash)
})
}
})
/* ------------------------------------------------------------------------ */
+90
View File
@@ -0,0 +1,90 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, ccxt = require ('../../../ccxt.js')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
// will try to place a buy order at the minimum price level on minimum amount possible
// will skip if balance is positive or market limits are not set
module.exports = async (exchange, symbol, balance) => {
if (!exchange.has.createOrder) {
log ('createOrder not supported')
return
}
const markets = await exchange.loadMarkets ()
const market = markets[symbol]
if (market.limits === undefined) {
log ('market.limits property is not set, will not test order creation')
return
}
const { price, amount, cost } = market.limits
if (price === undefined || amount === undefined || cost === undefined) {
log ('market.limits.[price|amount|cost] property is not set, will not test order creation')
return
}
let minPrice = price.min
let minAmount = amount.min // will be adjusted co cover minCost if needed
const minCost = cost.min
if (minPrice === undefined || minAmount === undefined || minCost === undefined) {
log ('min limits are not set, will not test order creation')
return
}
if (minCost > minPrice * minAmount) {
minAmount = minCost / minPrice
}
minPrice = exchange.priceToPrecision (symbol, minPrice)
minAmount = exchange.amountToPrecision (symbol, minAmount)
if (balance === undefined) {
log ('balance is not set, cannot ensure safety, will not test order creation')
return
}
const { base, quote } = market
if (balance[quote] !== undefined && balance[quote].total > 0) {
log ('balance is not empty, will not test order creation')
return
}
try {
log ('creating limit buy order...', symbol, minAmount, minPrice)
let order = await exchange.createLimitBuyOrder (symbol, minAmount, minPrice)
log ('order created although it should not had to - cleaning up')
log (order)
await exchange.cancelOrder (order.id, symbol)
assert.fail ()
} catch (e) {
if (e instanceof ccxt.InsufficientFunds) {
log ('InsufficientFunds thrown as expected')
} else {
log ('InsufficientFunds failed, exception follows:')
throw e
}
}
}
+67
View File
@@ -0,0 +1,67 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, ccxt = require ('../../../ccxt.js')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
log.green ('AuthenticationError (bad nonce) test...')
const hasFetchBalance = exchange.has.fetchBalance
const hasFetchMyTrades = exchange.has.fetchMyTrades
const hasFetchOrders = exchange.has.fetchOrders
if (hasFetchBalance || hasFetchMyTrades || hasFetchOrders) {
// save the nonce temporarily and replace it with a fake one
const nonce = exchange.nonce
exchange.nonce = () => 1
try {
// check if handleErrors() throws AuthenticationError if an exchange
// responds with an error on a bad nonce
// (still, some exchanges that require nonce silently eat bad nonce w/o an error)
if (hasFetchBalance)
await exchange.fetchBalance ()
else if (hasFetchMyTrades)
await exchange.fetchMyTrades (symbol, 0)
else
await exchange.fetchOrders (symbol)
// restore the nonce so the caller may proceed in case bad nonce was accepted by an exchange
exchange.nonce = nonce
log.warn (exchange.id + ': AuthenticationError: bad nonce swallowed')
} catch (e) {
// restore the nonce so the caller may proceed in case the test failed
exchange.nonce = nonce
if (e instanceof ccxt.AuthenticationError || e instanceof ccxt.InvalidNonce) {
// it has thrown the exception as expected
log.green ('AuthenticationError test passed')
} else {
// rethrow an unexpected error if any
throw e
}
}
} else {
log (exchange.id + ' has no means of testing for bad nonce')
}
}
+39
View File
@@ -0,0 +1,39 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, ccxt = require ('../../../ccxt.js')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
if (!exchange.has.createOrder) {
log ('createOrder not supported')
return
}
try {
await exchange.createLimitBuyOrder (symbol, 0, 0)
assert.fail ()
} catch (e) {
if (e instanceof ccxt.InvalidOrder) {
log ('InvalidOrder thrown as expected')
return
} else {
log ('InvalidOrder failed, exception follows:')
throw e
}
}
}
+43
View File
@@ -0,0 +1,43 @@
'use strict'
// ----------------------------------------------------------------------------
const log = require ('ololog')
, ansi = require ('ansicolor').nice
, chai = require ('chai')
, ccxt = require ('../../../ccxt.js')
, expect = chai.expect
, assert = chai.assert
/* ------------------------------------------------------------------------ */
module.exports = async (exchange, symbol) => {
if (!exchange.has.createOrder) {
log ('createOrder not supported -> test skipped')
return
}
let id = 1
try {
await exchange.cancelOrder (id, symbol)
log ('test failed')
assert.fail ()
} catch (e) {
if (e instanceof ccxt.OrderNotFound) {
log ('OrderNotFound thrown as expected')
} else {
log ('OrderNotFound test failed')
throw e
}
}
}
+379
View File
@@ -0,0 +1,379 @@
'use strict'
/* ------------------------------------------------------------------------ */
const [processPath, , exchangeId = null, exchangeSymbol = null] = process.argv.filter (x => !x.startsWith ('--'))
const verbose = process.argv.includes ('--verbose') || false
const debug = process.argv.includes ('--debug') || false
/* ------------------------------------------------------------------------ */
const asTable = require ('as-table')
, log = require ('ololog')
, ansi = require ('ansicolor').nice
, fs = require ('fs')
, ccxt = require ('../../ccxt.js')
, chai = require ('chai')
, assert = chai.assert
/* ------------------------------------------------------------------------ */
const warn = log.bright.yellow.error // .error → stderr
/* ------------------------------------------------------------------------ */
process.on ('uncaughtException', e => { log.bright.red.error (e); process.exit (1) })
process.on ('unhandledRejection', e => { log.bright.red.error (e); process.exit (1) })
/* ------------------------------------------------------------------------ */
log.bright ('\nTESTING', { exchange: exchangeId, symbol: exchangeSymbol || 'all' }, '\n')
/* ------------------------------------------------------------------------ */
let proxies = [
'',
'https://cors-anywhere.herokuapp.com/'
]
//-----------------------------------------------------------------------------
const enableRateLimit = true
const { Agent } = require ('https')
const agent = new Agent ({
ecdhCurve: 'auto',
})
const exchange = new (ccxt)[exchangeId] ({
agent,
verbose,
enableRateLimit,
debug,
timeout: 20000,
})
//-----------------------------------------------------------------------------
const tests = {}
const properties = Object.keys (exchange.has)
properties
.filter (property => fs.existsSync (__dirname + '/Exchange/test.' + property + '.js'))
.forEach (property => {
// eslint-disable-next-line import/no-dynamic-require
tests[property] = require (__dirname + '/Exchange/test.' + property + '.js')
})
const errors = require ('../base/errors.js')
Object.keys (errors)
.filter (error => fs.existsSync (__dirname + '/errors/test.' + error + '.js'))
.forEach (error => {
// eslint-disable-next-line import/no-dynamic-require
tests[error] = require (__dirname + '/errors/test.' + error + '.js')
})
//-----------------------------------------------------------------------------
const keysGlobal = 'keys.json'
const keysLocal = 'keys.local.json'
const keysFile = fs.existsSync (keysLocal) ? keysLocal : keysGlobal
// eslint-disable-next-line import/no-dynamic-require
const settings = require (__dirname + '/../../' + keysFile)[exchangeId]
if (settings) {
for (const key in settings) {
if (settings[key]) {
settings[key] = ccxt.deepExtend (exchange[key] || {}, settings[key])
}
}
}
Object.assign (exchange, settings)
if (settings && settings.skip) {
log.error.bright ('[Skipped]', { exchange: exchangeId, symbol: exchangeSymbol || 'all' })
process.exit ()
}
//-----------------------------------------------------------------------------
let testSymbol = async (exchange, symbol) => {
if (exchange.id !== 'coinmarketcap') {
await tests['fetchMarkets'] (exchange)
await tests['fetchCurrencies'] (exchange)
}
await tests['fetchTicker'] (exchange, symbol)
await tests['fetchTickers'] (exchange, symbol)
await tests['fetchOHLCV'] (exchange, symbol)
await tests['fetchTrades'] (exchange, symbol)
if (exchange.id === 'coinmarketcap') {
log (await exchange.fetchTickers ())
log (await exchange.fetchGlobal ())
} else if (exchange.id === 'coinbase') {
// nothing for now
} else {
await tests['fetchOrderBook'] (exchange, symbol)
await tests['fetchL2OrderBook'] (exchange, symbol)
await tests['fetchOrderBooks'] (exchange)
}
}
//-----------------------------------------------------------------------------
let loadExchange = async exchange => {
let markets = await exchange.loadMarkets ()
assert (typeof exchange.markets === 'object', '.markets is not an object')
assert (Array.isArray (exchange.symbols), '.symbols is not an array')
assert (exchange.symbols.length > 0, '.symbols.length <= 0 (less than or equal to zero)')
assert (Object.keys (exchange.markets).length > 0, 'Object.keys (.markets).length <= 0 (less than or equal to zero)')
assert (exchange.symbols.length === Object.keys (exchange.markets).length, 'number of .symbols is not equal to the number of .markets')
let symbols = [
'BTC/CNY',
'BTC/USD',
'BTC/USDT',
'BTC/EUR',
'BTC/ETH',
'ETH/BTC',
'BTC/JPY',
'ETH/EUR',
'ETH/JPY',
'ETH/CNY',
'LTC/CNY',
'DASH/BTC',
'DOGE/BTC',
'BTC/AUD',
'BTC/PLN',
'USD/SLL',
'BTC/RUB',
'BTC/UAH',
'LTC/BTC',
]
let result = exchange.symbols.filter (symbol => symbols.indexOf (symbol) >= 0)
if (result.length > 0)
if (exchange.symbols.length > result.length)
result = result.join (', ') + ' + more...'
else
result = result.join (', ')
log (exchange.symbols.length.toString ().bright.green, 'symbols', result)
}
//-----------------------------------------------------------------------------
let testExchange = async exchange => {
const codes = [
'BTC',
'ETH',
'XRP',
'LTC',
'BCH',
'EOS',
'BNB',
'BSV',
'USDT',
'ATOM',
'BAT',
'BTG',
'DASH',
'DOGE',
'ETC',
'IOTA',
'LSK',
'MKR',
'NEO',
'PAX',
'QTUM',
'TRX',
'TUSD',
'USD',
'USDC',
'WAVES',
'XEM',
'XMR',
'ZEC',
'ZRX',
]
let code = codes[0]
for (let c in codes) {
if (c in exchange.currencies) {
code = c
}
}
await loadExchange (exchange)
let symbol = exchange.symbols[0]
const symbols = [
'BTC/USD',
'BTC/USDT',
'BTC/CNY',
'BTC/EUR',
'BTC/ETH',
'ETH/BTC',
'BTC/JPY',
'LTC/BTC',
'ZRX/WETH',
]
for (let s in symbols) {
if (exchange.symbols.includes (symbols[s]) &&
(('active' in exchange.markets[symbols[s]]) ? exchange.markets[symbols[s]]['active'] : true)) {
symbol = symbols[s]
break
}
}
if (exchange.id === 'okex') {
// okex has different order creation params for spot and futures markets
// this will stick okex to spot market until there is a way to test
// several markets per exchange
symbol = 'BTC/USDT'
}
log.green ('SYMBOL:', symbol)
if ((symbol.indexOf ('.d') < 0)) {
await testSymbol (exchange, symbol)
}
if (!exchange.privateKey && (!exchange.apiKey || (exchange.apiKey.length < 1)))
return true
exchange.checkRequiredCredentials ()
// move to testnet/sandbox if possible before accessing the balance
// if (exchange.urls['test'])
// exchange.urls['api'] = exchange.urls['test']
let balance = await tests['fetchBalance'] (exchange)
await tests['fetchFundingFees'] (exchange)
await tests['fetchTradingFees'] (exchange)
await tests['fetchStatus'] (exchange)
await tests['fetchOrders'] (exchange, symbol)
await tests['fetchOpenOrders'] (exchange, symbol)
await tests['fetchClosedOrders'] (exchange, symbol)
await tests['fetchMyTrades'] (exchange, symbol)
if ('fetchLedger' in tests) {
await tests['fetchLedger'] (exchange, code)
}
// const code = exchange.markets[symbol]['quote']
// await tests['fetchTransactions'] (exchange, code)
// await tests['fetchDeposits'] (exchange, code)
// await tests['fetchWithdrawals'] (exchange, code)
if (exchange.extendedTest) {
await tests['InvalidNonce'] (exchange, symbol)
await tests['OrderNotFound'] (exchange, symbol)
await tests['InvalidOrder'] (exchange, symbol)
await tests['InsufficientFunds'] (exchange, symbol, balance) // danger zone - won't execute with non-empty balance
}
// try {
// let marketSellOrder =
// await exchange.createMarketSellOrder (exchange.symbols[0], 1)
// console.log (exchange.id, 'ok', marketSellOrder)
// } catch (e) {
// console.log (exchange.id, 'error', 'market sell', e)
// }
// try {
// let marketBuyOrder = await exchange.createMarketBuyOrder (exchange.symbols[0], 1)
// console.log (exchange.id, 'ok', marketBuyOrder)
// } catch (e) {
// console.log (exchange.id, 'error', 'market buy', e)
// }
// try {
// let limitSellOrder = await exchange.createLimitSellOrder (exchange.symbols[0], 1, 3000)
// console.log (exchange.id, 'ok', limitSellOrder)
// } catch (e) {
// console.log (exchange.id, 'error', 'limit sell', e)
// }
// try {
// let limitBuyOrder = await exchange.createLimitBuyOrder (exchange.symbols[0], 1, 3000)
// console.log (exchange.id, 'ok', limitBuyOrder)
// } catch (e) {
// console.log (exchange.id, 'error', 'limit buy', e)
// }
}
//-----------------------------------------------------------------------------
let tryAllProxies = async function (exchange, proxies) {
let currentProxy = 0
let maxRetries = proxies.length
if (settings && ('proxy' in settings))
currentProxy = proxies.indexOf (settings.proxy)
for (let numRetries = 0; numRetries < maxRetries; numRetries++) {
try {
exchange.proxy = proxies[currentProxy]
// add random origin for proxies
if (exchange.proxy.length > 0) {
exchange.origin = exchange.uuid ()
}
await testExchange (exchange)
break
} catch (e) {
currentProxy = ++currentProxy % proxies.length
if (e instanceof ccxt.DDoSProtection) {
warn ('[DDoS Protection]' + e.message.slice (0, 200))
} else if (e instanceof ccxt.RequestTimeout) {
warn ('[Request Timeout] ' + e.message.slice (0, 200))
} else if (e instanceof ccxt.ExchangeNotAvailable) {
warn ('[Exchange Not Available] ' + e.message.slice (0, 200))
} else if (e instanceof ccxt.AuthenticationError) {
warn ('[Authentication Error] ' + e.message.slice (0, 200))
return
} else if (e instanceof ccxt.InvalidNonce) {
warn ('[Invalid Nonce] ' + e.message.slice (0, 200))
return
} else {
throw e
}
}
}
}
//-----------------------------------------------------------------------------
;(async function test () {
if (exchangeSymbol) {
await loadExchange (exchange)
await testSymbol (exchange, exchangeSymbol)
} else {
await tryAllProxies (exchange, proxies)
}
}) ()
+12
View File
@@ -0,0 +1,12 @@
// run with `node test_timeout_hang`
// TODO: integrate with CI tests somehow...
const { timeout } = require ('../base/functions')
;(async function () {
await timeout (10000, Promise.resolve ('foo'))
console.log ('Look ma, no hangs!') // should terminate the process immediately..
}) ()