New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

bitfinex-api-node

Package Overview
Dependencies
Maintainers
4
Versions
71
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bitfinex-api-node - npm Package Compare versions

Comparing version
5.0.1
to
5.0.2
+12
-20
package.json
{
"name": "bitfinex-api-node",
"version": "5.0.1",
"version": "5.0.2",
"description": "Node reference library for Bitfinex API",

@@ -15,7 +15,6 @@ "engines": {

"scripts": {
"lint": "eslint lib/ examples/ test/ index.js",
"lint": "standard",
"test": "npm run lint && npm run unit",
"unit": "NODE_ENV=test mocha -b --recursive",
"docs": "rm -rf docs && node_modules/.bin/jsdoc --configure .jsdoc.json --verbose",
"build": "babel -q ./index.js -d ./dist && babel -q ./lib -d ./dist/lib && copy package.json dist"
"docs": "rm -rf docs && node_modules/.bin/jsdoc --configure .jsdoc.json --verbose"
},

@@ -46,5 +45,2 @@ "repository": {

"devDependencies": {
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-regenerator": "^6.26.0",
"babel-preset-env": "^1.7.0",
"bfx-api-mock-srv": "^1.0.4",

@@ -54,10 +50,2 @@ "chai": "^4.2.0",

"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsdoc": "^22.0.0",
"eslint-plugin-lodash": "^6.0.0",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"husky": "^4.2.3",

@@ -67,3 +55,3 @@ "jsdoc-to-markdown": "^5.0.1",

"socks-proxy-agent": "^5.0.0",
"standard": "^14.3.1"
"standard": "^16.0.3"
},

@@ -75,5 +63,5 @@ "dependencies": {

"cli-table3": "^0.6.0",
"bfx-api-node-models": "^1.3.1",
"bfx-api-node-rest": "^4.0.0",
"bfx-api-node-util": "^1.0.2",
"bfx-api-node-models": "^1.4.0",
"bfx-api-node-rest": "^4.1.2",
"bfx-api-node-util": "^1.0.10",
"bfx-api-node-ws1": "^1.0.0",

@@ -83,3 +71,2 @@ "bignumber.js": "^9.0.0",

"cbq": "0.0.1",
"copy": "^0.3.2",
"crc-32": "^1.2.0",

@@ -95,3 +82,8 @@ "debug": "^4.1.1",

"ws": "^7.2.1"
},
"standard": {
"ignore": [
"/docs/**/*.js"
]
}
}
{
"presets": [
"env"
],
"plugins": [
"transform-regenerator",
"transform-object-rest-spread"
]
}
{
"plugins": [
"jsdoc"
],
"env": {
"es6": true,
"node": true,
"es2020": true
},
"extends": [
"standard",
"eslint:recommended",
"plugin:promise/recommended",
"plugin:node/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:lodash/recommended",
"plugin:jsdoc/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
"promise/no-callback-in-promise": 0,
"lodash/prefer-lodash-method": 0,
"lodash/prefer-noop": 0,
"lodash/prefer-constant": 0,
"node/no-unpublished-require": 0
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

verbose: false
instrumentation:
root: .
extensions:
- .js
default-excludes: true
excludes: []
embed-source: false
variable: __coverage__
compact: true
preserve-comments: false
complete-copy: false
save-baseline: false
baseline-file: ./coverage/coverage-baseline.json
include-all-sources: false
include-pid: false
reporting:
print: summary
reports:
- lcov
dir: ./source/images/coverage
watermarks:
statements: [50, 80]
lines: [50, 80]
functions: [50, 80]
branches: [50, 80]
report-config:
clover: {file: clover.xml}
cobertura: {file: cobertura-coverage.xml}
json: {file: coverage-final.json}
json-summary: {file: coverage-summary.json}
lcovonly: {file: lcov.info}
teamcity: {file: null, blockName: Code Coverage Summary}
text: {file: null, maxCols: 0}
text-lcov: {file: lcov.info}
text-summary: {file: null}
hooks:
hook-run-in-context: false
post-require-hook: null
handle-sigint: false
check:
global:
statements: 0
lines: 0
branches: 0
functions: 0
excludes: []
each:
statements: 0
lines: 0
branches: 0
functions: 0
excludes: []
{
"tags": {
"allowUnknownTags": false,
"dictionaries": ["jsdoc"]
},
"source": {
"include": ["lib", "LICENSE.md", "README.md"],
"includePattern": ".js$",
"excludePattern": "(node_modules/|docs)"
},
"plugins": [
"plugins/markdown"
],
"templates": {
"cleverLinks": false,
"monospaceLinks": true,
"useLongnameInNav": false,
"showInheritedInNav": true
},
"opts": {
"destination": "./docs/",
"encoding": "utf8",
"recurse": true,
"template": "./node_modules/docdash"
},
"package": ""
}
sudo: false
language: node_js
node_js:
- "stable"
install:
- npm install
script:
- npm run lint
- npm run unit

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Global - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WS2Manager.html">WS2Manager</a><ul class='methods'><li data-type='method'><a href="WS2Manager.html#.getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WS2Manager.html#auth">auth</a></li><li data-type='method'><a href="WS2Manager.html#close">close</a></li><li data-type='method'><a href="WS2Manager.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#getAuthenticatedSocket">getAuthenticatedSocket</a></li><li data-type='method'><a href="WS2Manager.html#getFreeDataSocket">getFreeDataSocket</a></li><li data-type='method'><a href="WS2Manager.html#getNumSockets">getNumSockets</a></li><li data-type='method'><a href="WS2Manager.html#getSocket">getSocket</a></li><li data-type='method'><a href="WS2Manager.html#getSocketInfo">getSocketInfo</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithChannel">getSocketWithChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithDataChannel">getSocketWithDataChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithSubRef">getSocketWithSubRef</a></li><li data-type='method'><a href="WS2Manager.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#onCandle">onCandle</a></li><li data-type='method'><a href="WS2Manager.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#onTicker">onTicker</a></li><li data-type='method'><a href="WS2Manager.html#onTrades">onTrades</a></li><li data-type='method'><a href="WS2Manager.html#openSocket">openSocket</a></li><li data-type='method'><a href="WS2Manager.html#reconnect">reconnect</a></li><li data-type='method'><a href="WS2Manager.html#setAuthArgs">setAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#subscribe">subscribe</a></li><li data-type='method'><a href="WS2Manager.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WS2Manager.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WS2Manager.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#withAllSockets">withAllSockets</a></li></ul></li><li><a href="WSv2.html">WSv2</a><ul class='methods'><li data-type='method'><a href="WSv2.html#auth">auth</a></li><li data-type='method'><a href="WSv2.html#cancelOrder">cancelOrder</a></li><li data-type='method'><a href="WSv2.html#cancelOrders">cancelOrders</a></li><li data-type='method'><a href="WSv2.html#close">close</a></li><li data-type='method'><a href="WSv2.html#enableFlag">enableFlag</a></li><li data-type='method'><a href="WSv2.html#enableSequencing">enableSequencing</a></li><li data-type='method'><a href="WSv2.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WSv2.html#getCandles">getCandles</a></li><li data-type='method'><a href="WSv2.html#getChannelData">getChannelData</a></li><li data-type='method'><a href="WSv2.html#getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WSv2.html#getDataChannelId">getDataChannelId</a></li><li data-type='method'><a href="WSv2.html#getLosslessOB">getLosslessOB</a></li><li data-type='method'><a href="WSv2.html#getOB">getOB</a></li><li data-type='method'><a href="WSv2.html#getURL">getURL</a></li><li data-type='method'><a href="WSv2.html#hasChannel">hasChannel</a></li><li data-type='method'><a href="WSv2.html#hasDataChannel">hasDataChannel</a></li><li data-type='method'><a href="WSv2.html#hasSubscriptionRef">hasSubscriptionRef</a></li><li data-type='method'><a href="WSv2.html#isAuthenticated">isAuthenticated</a></li><li data-type='method'><a href="WSv2.html#isFlagEnabled">isFlagEnabled</a></li><li data-type='method'><a href="WSv2.html#isOpen">isOpen</a></li><li data-type='method'><a href="WSv2.html#isReconnecting">isReconnecting</a></li><li data-type='method'><a href="WSv2.html#managedSubscribe">managedSubscribe</a></li><li data-type='method'><a href="WSv2.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WSv2.html#notifyUI">notifyUI</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeEntry">onAccountTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeUpdate">onAccountTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onBalanceInfoUpdate">onBalanceInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onCandle">onCandle</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditClose">onFundingCreditClose</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditNew">onFundingCreditNew</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditSnapshot">onFundingCreditSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditUpdate">onFundingCreditUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingInfoUpdate">onFundingInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanClose">onFundingLoanClose</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanNew">onFundingLoanNew</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanSnapshot">onFundingLoanSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanUpdate">onFundingLoanUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferClose">onFundingOfferClose</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferNew">onFundingOfferNew</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferSnapshot">onFundingOfferSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferUpdate">onFundingOfferUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeEntry">onFundingTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeUpdate">onFundingTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onInfoMessage">onInfoMessage</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceEnd">onMaintenanceEnd</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceStart">onMaintenanceStart</a></li><li data-type='method'><a href="WSv2.html#onMarginInfoUpdate">onMarginInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onMessage">onMessage</a></li><li data-type='method'><a href="WSv2.html#onNotification">onNotification</a></li><li data-type='method'><a href="WSv2.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WSv2.html#onOrderBookChecksum">onOrderBookChecksum</a></li><li data-type='method'><a href="WSv2.html#onOrderClose">onOrderClose</a></li><li data-type='method'><a href="WSv2.html#onOrderNew">onOrderNew</a></li><li data-type='method'><a href="WSv2.html#onOrderSnapshot">onOrderSnapshot</a></li><li data-type='method'><a href="WSv2.html#onOrderUpdate">onOrderUpdate</a></li><li data-type='method'><a href="WSv2.html#onPositionClose">onPositionClose</a></li><li data-type='method'><a href="WSv2.html#onPositionNew">onPositionNew</a></li><li data-type='method'><a href="WSv2.html#onPositionSnapshot">onPositionSnapshot</a></li><li data-type='method'><a href="WSv2.html#onPositionUpdate">onPositionUpdate</a></li><li data-type='method'><a href="WSv2.html#onServerRestart">onServerRestart</a></li><li data-type='method'><a href="WSv2.html#onStatus">onStatus</a></li><li data-type='method'><a href="WSv2.html#onTicker">onTicker</a></li><li data-type='method'><a href="WSv2.html#onTradeEntry">onTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onTrades">onTrades</a></li><li data-type='method'><a href="WSv2.html#onWalletSnapshot">onWalletSnapshot</a></li><li data-type='method'><a href="WSv2.html#onWalletUpdate">onWalletUpdate</a></li><li data-type='method'><a href="WSv2.html#open">open</a></li><li data-type='method'><a href="WSv2.html#reconnect">reconnect</a></li><li data-type='method'><a href="WSv2.html#removeListeners">removeListeners</a></li><li data-type='method'><a href="WSv2.html#requestCalc">requestCalc</a></li><li data-type='method'><a href="WSv2.html#send">send</a></li><li data-type='method'><a href="WSv2.html#sequencingEnabled">sequencingEnabled</a></li><li data-type='method'><a href="WSv2.html#setAPICredentials">setAPICredentials</a></li><li data-type='method'><a href="WSv2.html#submitOrder">submitOrder</a></li><li data-type='method'><a href="WSv2.html#submitOrderMultiOp">submitOrderMultiOp</a></li><li data-type='method'><a href="WSv2.html#subscribe">subscribe</a></li><li data-type='method'><a href="WSv2.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WSv2.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#subscribeStatus">subscribeStatus</a></li><li data-type='method'><a href="WSv2.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WSv2.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WSv2.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WSv2.html#unsubscribeCandles">unsubscribeCandles</a></li><li data-type='method'><a href="WSv2.html#unsubscribeOrderBook">unsubscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#unsubscribeStatus">unsubscribeStatus</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTicker">unsubscribeTicker</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTrades">unsubscribeTrades</a></li><li data-type='method'><a href="WSv2.html#updateAuthArgs">updateAuthArgs</a></li><li data-type='method'><a href="WSv2.html#updateOrder">updateOrder</a></li><li data-type='method'><a href="WSv2.html#usesAgent">usesAgent</a></li></ul></li><li></li></ul><h3>Global</h3><ul><li><a href="global.html#setSigFig">setSigFig</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">Global</h1>
<section>
<header>
<h2>
</h2>
</header>
<article>
<div class="container-overview">
<dl class="details">
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="setSigFig"><span class="type-signature"></span>setSigFig<span class="signature">(number, maxSigs<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {string}</span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="util_precision.js.html">util/precision.js</a>, <a href="util_precision.js.html#line17">line 17</a>
</li></ul></dd>
</dl>
<div class="description usertext">
<p>Smartly set the precision (decimal) on a value based off of the significant
digit maximum. For example, calling with 3.34 when the max sig figs allowed
is 5 would return '3.3400', the representation number of decimals IF they
weren't zeros.</p>
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Attributes</th>
<th>Default</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>number</code></td>
<td class="type">
<span class="param-type">number</span>
</td>
<td class="attributes">
</td>
<td class="default">
<code>0</code>
</td>
<td class="description last"><p>number to manipulate</p></td>
</tr>
<tr>
<td class="name"><code>maxSigs</code></td>
<td class="type">
<span class="param-type">number</span>
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="default">
</td>
<td class="description last"><p>default 5</p></td>
</tr>
</tbody>
</table>
<h5>Returns:</h5>
<div class="param-desc">
<p>str</p>
</div>
<dl class="param-type">
<dt>
Type
</dt>
<dd>
<span class="param-type">string</span>
</dd>
</dl>
<h3 class="subsection-title">Type Definitions</h3>
<h4 class="name" id="PromiseThrottle">PromiseThrottle</h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="transports_ws2.js.html">transports/ws2.js</a>, <a href="transports_ws2.js.html#line45">line 45</a>
</li></ul></dd>
</dl>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>add</code></td>
<td class="type">
<span class="param-type">function</span>
</td>
<td class="description last"><p>add a promise to be throttled</p></td>
</tr>
</tbody>
</table>
<div class="description usertext">
<p>A Promise Throttle instance</p>
</div>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">object</span>
</li>
</ul>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.6</a> on Mon Jan 25 2021 16:36:37 GMT+0100 (Central European Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WS2Manager.html">WS2Manager</a><ul class='methods'><li data-type='method'><a href="WS2Manager.html#.getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WS2Manager.html#auth">auth</a></li><li data-type='method'><a href="WS2Manager.html#close">close</a></li><li data-type='method'><a href="WS2Manager.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#getAuthenticatedSocket">getAuthenticatedSocket</a></li><li data-type='method'><a href="WS2Manager.html#getFreeDataSocket">getFreeDataSocket</a></li><li data-type='method'><a href="WS2Manager.html#getNumSockets">getNumSockets</a></li><li data-type='method'><a href="WS2Manager.html#getSocket">getSocket</a></li><li data-type='method'><a href="WS2Manager.html#getSocketInfo">getSocketInfo</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithChannel">getSocketWithChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithDataChannel">getSocketWithDataChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithSubRef">getSocketWithSubRef</a></li><li data-type='method'><a href="WS2Manager.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#onCandle">onCandle</a></li><li data-type='method'><a href="WS2Manager.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#onTicker">onTicker</a></li><li data-type='method'><a href="WS2Manager.html#onTrades">onTrades</a></li><li data-type='method'><a href="WS2Manager.html#openSocket">openSocket</a></li><li data-type='method'><a href="WS2Manager.html#reconnect">reconnect</a></li><li data-type='method'><a href="WS2Manager.html#setAuthArgs">setAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#subscribe">subscribe</a></li><li data-type='method'><a href="WS2Manager.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WS2Manager.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WS2Manager.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#withAllSockets">withAllSockets</a></li></ul></li><li><a href="WSv2.html">WSv2</a><ul class='methods'><li data-type='method'><a href="WSv2.html#auth">auth</a></li><li data-type='method'><a href="WSv2.html#cancelOrder">cancelOrder</a></li><li data-type='method'><a href="WSv2.html#cancelOrders">cancelOrders</a></li><li data-type='method'><a href="WSv2.html#close">close</a></li><li data-type='method'><a href="WSv2.html#enableFlag">enableFlag</a></li><li data-type='method'><a href="WSv2.html#enableSequencing">enableSequencing</a></li><li data-type='method'><a href="WSv2.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WSv2.html#getCandles">getCandles</a></li><li data-type='method'><a href="WSv2.html#getChannelData">getChannelData</a></li><li data-type='method'><a href="WSv2.html#getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WSv2.html#getDataChannelId">getDataChannelId</a></li><li data-type='method'><a href="WSv2.html#getLosslessOB">getLosslessOB</a></li><li data-type='method'><a href="WSv2.html#getOB">getOB</a></li><li data-type='method'><a href="WSv2.html#getURL">getURL</a></li><li data-type='method'><a href="WSv2.html#hasChannel">hasChannel</a></li><li data-type='method'><a href="WSv2.html#hasDataChannel">hasDataChannel</a></li><li data-type='method'><a href="WSv2.html#hasSubscriptionRef">hasSubscriptionRef</a></li><li data-type='method'><a href="WSv2.html#isAuthenticated">isAuthenticated</a></li><li data-type='method'><a href="WSv2.html#isFlagEnabled">isFlagEnabled</a></li><li data-type='method'><a href="WSv2.html#isOpen">isOpen</a></li><li data-type='method'><a href="WSv2.html#isReconnecting">isReconnecting</a></li><li data-type='method'><a href="WSv2.html#managedSubscribe">managedSubscribe</a></li><li data-type='method'><a href="WSv2.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WSv2.html#notifyUI">notifyUI</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeEntry">onAccountTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeUpdate">onAccountTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onBalanceInfoUpdate">onBalanceInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onCandle">onCandle</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditClose">onFundingCreditClose</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditNew">onFundingCreditNew</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditSnapshot">onFundingCreditSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditUpdate">onFundingCreditUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingInfoUpdate">onFundingInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanClose">onFundingLoanClose</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanNew">onFundingLoanNew</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanSnapshot">onFundingLoanSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanUpdate">onFundingLoanUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferClose">onFundingOfferClose</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferNew">onFundingOfferNew</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferSnapshot">onFundingOfferSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferUpdate">onFundingOfferUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeEntry">onFundingTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeUpdate">onFundingTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onInfoMessage">onInfoMessage</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceEnd">onMaintenanceEnd</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceStart">onMaintenanceStart</a></li><li data-type='method'><a href="WSv2.html#onMarginInfoUpdate">onMarginInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onMessage">onMessage</a></li><li data-type='method'><a href="WSv2.html#onNotification">onNotification</a></li><li data-type='method'><a href="WSv2.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WSv2.html#onOrderBookChecksum">onOrderBookChecksum</a></li><li data-type='method'><a href="WSv2.html#onOrderClose">onOrderClose</a></li><li data-type='method'><a href="WSv2.html#onOrderNew">onOrderNew</a></li><li data-type='method'><a href="WSv2.html#onOrderSnapshot">onOrderSnapshot</a></li><li data-type='method'><a href="WSv2.html#onOrderUpdate">onOrderUpdate</a></li><li data-type='method'><a href="WSv2.html#onPositionClose">onPositionClose</a></li><li data-type='method'><a href="WSv2.html#onPositionNew">onPositionNew</a></li><li data-type='method'><a href="WSv2.html#onPositionSnapshot">onPositionSnapshot</a></li><li data-type='method'><a href="WSv2.html#onPositionUpdate">onPositionUpdate</a></li><li data-type='method'><a href="WSv2.html#onServerRestart">onServerRestart</a></li><li data-type='method'><a href="WSv2.html#onStatus">onStatus</a></li><li data-type='method'><a href="WSv2.html#onTicker">onTicker</a></li><li data-type='method'><a href="WSv2.html#onTradeEntry">onTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onTrades">onTrades</a></li><li data-type='method'><a href="WSv2.html#onWalletSnapshot">onWalletSnapshot</a></li><li data-type='method'><a href="WSv2.html#onWalletUpdate">onWalletUpdate</a></li><li data-type='method'><a href="WSv2.html#open">open</a></li><li data-type='method'><a href="WSv2.html#reconnect">reconnect</a></li><li data-type='method'><a href="WSv2.html#removeListeners">removeListeners</a></li><li data-type='method'><a href="WSv2.html#requestCalc">requestCalc</a></li><li data-type='method'><a href="WSv2.html#send">send</a></li><li data-type='method'><a href="WSv2.html#sequencingEnabled">sequencingEnabled</a></li><li data-type='method'><a href="WSv2.html#setAPICredentials">setAPICredentials</a></li><li data-type='method'><a href="WSv2.html#submitOrder">submitOrder</a></li><li data-type='method'><a href="WSv2.html#submitOrderMultiOp">submitOrderMultiOp</a></li><li data-type='method'><a href="WSv2.html#subscribe">subscribe</a></li><li data-type='method'><a href="WSv2.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WSv2.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#subscribeStatus">subscribeStatus</a></li><li data-type='method'><a href="WSv2.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WSv2.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WSv2.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WSv2.html#unsubscribeCandles">unsubscribeCandles</a></li><li data-type='method'><a href="WSv2.html#unsubscribeOrderBook">unsubscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#unsubscribeStatus">unsubscribeStatus</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTicker">unsubscribeTicker</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTrades">unsubscribeTrades</a></li><li data-type='method'><a href="WSv2.html#updateAuthArgs">updateAuthArgs</a></li><li data-type='method'><a href="WSv2.html#updateOrder">updateOrder</a></li><li data-type='method'><a href="WSv2.html#usesAgent">usesAgent</a></li></ul></li><li></li></ul><h3>Global</h3><ul><li><a href="global.html#setSigFig">setSigFig</a></li></ul>
</nav>
<div id="main">
<section class="package">
<h3> </h3>
</section>
<section class="readme usertext">
<article><h1>Bitfinex WSv2 Trading API for Node.JS - Bitcoin, Ethereum, Ripple and more</h1>
<p><a href="https://travis-ci.org/bitfinexcom/bitfinex-api-node"><img src="https://travis-ci.org/bitfinexcom/bitfinex-api-node.svg?branch=master" alt="Build Status"></a></p>
<p>A Node.JS reference implementation of the Bitfinex API</p>
<h2>Features</h2>
<ul>
<li>Official implementation</li>
<li>REST v2 API</li>
<li>WebSockets v2 API</li>
<li>WebSockets v1 API</li>
</ul>
<h2>Installation</h2>
<pre class="prettyprint source lang-bash"><code> npm i --save bitfinex-api-node
</code></pre>
<h3>Quickstart</h3>
<pre class="prettyprint source lang-js"><code>const { WSv2 } = require('bitfinex-api-node')
const ws = new WSv2({ transform: true })
// do something with ws client
</code></pre>
<h3>Docs</h3>
<p>Refer to the <a href="https://cdn.statically.io/gh/bitfinexcom/bitfinex-api-node/master/docs/index.html"><code>docs/</code></a>
folder for JSDoc-generated HTML documentation, and the <a href="/examples"><code>examples/</code></a>
folder for executable examples covering common use cases.</p>
<p>Official API documentation at <a href="https://docs.bitfinex.com/v2/reference">https://docs.bitfinex.com/v2/reference</a></p>
<h3>Examples</h3>
<p>Sending an order &amp; tracking status:</p>
<pre class="prettyprint source lang-js"><code>const ws = bfx.ws()
ws.on('error', (err) => console.log(err))
ws.on('open', ws.auth.bind(ws))
ws.once('auth', () => {
const o = new Order({
cid: Date.now(),
symbol: 'tETHUSD',
amount: 0.1,
type: Order.type.MARKET
}, ws)
// Enable automatic updates
o.registerListeners()
o.on('update', () => {
console.log(`order updated: ${o.serialize()}`)
})
o.on('close', () => {
console.log(`order closed: ${o.status}`)
ws.close()
})
o.submit().then(() => {
console.log(`submitted order ${o.id}`)
}).catch((err) => {
console.error(err)
ws.close()
})
})
ws.open()
</code></pre>
<p>Cancel all open orders</p>
<pre class="prettyprint source lang-js"><code>const ws = bfx.ws(2)
ws.on('error', (err) => console.log(err))
ws.on('open', ws.auth.bind(ws))
ws.onOrderSnapshot({}, (orders) => {
if (orders.length === 0) {
console.log('no open orders')
return
}
console.log(`recv ${orders.length} open orders`)
ws.cancelOrders(orders).then(() => {
console.log('cancelled orders')
})
})
ws.open()
</code></pre>
<p>Subscribe to trades by pair</p>
<pre class="prettyprint source lang-js"><code>const ws = bfx.ws(2)
ws.on('error', (err) => console.log(err))
ws.on('open', () => {
ws.subscribeTrades('BTCUSD')
})
ws.onTrades({ symbol: 'tBTCUSD' }, (trades) => {
console.log(`trades: ${JSON.stringify(trades)}`)
})
ws.onTradeEntry({ symbol: 'tBTCUSD' }, (trades) => {
console.log(`te: ${JSON.stringify(trades)}`)
})
ws.open()
</code></pre>
<h2>Version 2.0.0 Breaking changes</h2>
<h3>constructor takes only an options object now, including the API keys</h3>
<p>Old:</p>
<pre class="prettyprint source lang-js"><code>new BFX(API_KEY, API_SECRET, { version: 2 })
</code></pre>
<p>since 2.0.0:</p>
<pre class="prettyprint source lang-js"><code>new BFX({ apiKey: '', apiSecret: '' })
</code></pre>
<h3><code>trade</code> and <code>orderbook</code> snapshots are emitted as nested lists</h3>
<p>To make dealing with snapshots better predictable, snapshots are emitted as an array.</p>
<h3>normalized orderbooks for R0</h3>
<p>Lists of raw orderbooks (<code>R0</code>) are ordered in the same order as <code>P0</code>, <code>P1</code>,
<code>P2</code>, <code>P3</code></p>
<h2>Testing</h2>
<pre class="prettyprint source lang-bash"><code>npm test
</code></pre>
<h2>FAQ</h2>
<h3>Order Creation Limits</h3>
<p>The base limit per-user is 1,000 orders per 5 minute interval, and is shared
between all account API connections. It increases proportionally to your trade
volume based on the following formula:</p>
<p><code>1000 + (TOTAL_PAIRS_PLATFORM * 60 * 5) / (250000000 / USER_VOL_LAST_30d)</code></p>
<p>Where <code>TOTAL_PAIRS_PLATFORM</code> is the number of pairs shared between
Ethfinex/Bitfinex (currently ~101) and <code>USER_VOL_LAST_30d</code> is in USD.</p>
<h3>'on' Packet Guarantees</h3>
<p>No; if your order fills immediately, the first packet referencing the order
will be an <code>oc</code> signaling the order has closed. If the order fills partially
immediately after creation, an <code>on</code> packet will arrive with a status of
<code>PARTIALLY FILLED...</code></p>
<p>For example, if you submit a <code>LIMIT</code> buy for 0.2 BTC and it is added to the
order book, an <code>on</code> packet will arrive via ws2. After a partial fill of 0.1
BTC, an <code>ou</code> packet will arrive, followed by a final <code>oc</code> after the remaining
0.1 BTC fills.</p>
<p>On the other hand, if the order fills immediately for 0.2 BTC, you will only
receive an <code>oc</code> packet.</p>
<h3>Nonce too small</h3>
<p>I make multiple parallel request and I receive an error that the nonce is too
small. What does it mean?</p>
<p>Nonces are used to guard against replay attacks. When multiple HTTP requests
arrive at the API with the wrong nonce, e.g. because of an async timing issue,
the API will reject the request.</p>
<p>If you need to go parallel, you have to use multiple API keys right now.</p>
<h3><code>te</code> vs <code>tu</code> Messages</h3>
<p>A <code>te</code> packet is sent first to the client immediately after a trade has been
matched &amp; executed, followed by a <code>tu</code> message once it has completed processing.
During times of high load, the <code>tu</code> message may be noticably delayed, and as
such only the <code>te</code> message should be used for a realtime feed.</p>
<h3>Sequencing</h3>
<p>If you enable sequencing on v2 of the WS API, each incoming packet will have a
public sequence number at the end, along with an auth sequence number in the
case of channel <code>0</code> packets. The public seq numbers increment on each packet,
and the auth seq numbers increment on each authenticated action (new orders,
etc). These values allow you to verify that no packets have been missed/dropped,
since they always increase monotonically.</p>
<h3>Differences Between R* and P* Order Books</h3>
<p>Order books with precision <code>R0</code> are considered 'raw' and contain entries for
each order submitted to the book, whereas <code>P*</code> books contain entries for each
price level (which aggregate orders).</p>
<h3>Contributing</h3>
<ol>
<li>Fork it</li>
<li>Create your feature branch (<code>git checkout -b my-new-feature</code>)</li>
<li>Commit your changes (<code>git commit -am 'Add some feature'</code>)</li>
<li>Push to the branch (<code>git push origin my-new-feature</code>)</li>
<li>Create a new Pull Request</li>
</ol></article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.6</a> on Mon Jan 25 2021 16:36:37 GMT+0100 (Central European Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
</body>
</html>
function hideAllButCurrent(){
//by default all submenut items are hidden
//but we need to rehide them for search
document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) {
parent.style.display = "none";
});
//only current page (if it exists) should be opened
var file = window.location.pathname.split("/").pop().replace(/\.html/, '');
document.querySelectorAll("nav > ul > li > a").forEach(function(parent) {
var href = parent.attributes.href.value.replace(/\.html/, '');
if (file === href) {
parent.parentNode.querySelectorAll("ul li").forEach(function(elem) {
elem.style.display = "block";
});
}
});
}
hideAllButCurrent();
/*global document */
(function() {
var source = document.getElementsByClassName('prettyprint source linenums');
var i = 0;
var lineNumber = 0;
var lineId;
var lines;
var totalLines;
var anchorHash;
if (source && source[0]) {
anchorHash = document.location.hash.substring(1);
lines = source[0].getElementsByTagName('li');
totalLines = lines.length;
for (; i < totalLines; i++) {
lineNumber++;
lineId = 'line' + lineNumber;
lines[i].id = lineId;
if (lineId === anchorHash) {
lines[i].className += ' selected';
}
}
}
})();
function scrollToNavItem() {
var path = window.location.href.split('/').pop().replace(/\.html/, '');
document.querySelectorAll('nav a').forEach(function(link) {
var href = link.attributes.href.value.replace(/\.html/, '');
if (path === href) {
link.scrollIntoView({block: 'center'});
return;
}
})
}
scrollToNavItem();
//IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/
if (typeof(NodeList.prototype.forEach)!==typeof(alert)){
NodeList.prototype.forEach=Array.prototype.forEach;
}
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
var searchAttr = 'data-search-mode';
function contains(a,m){
return (a.textContent || a.innerText || "").toUpperCase().indexOf(m) !== -1;
};
//on search
document.getElementById("nav-search").addEventListener("keyup", function(event) {
var search = this.value.toUpperCase();
if (!search) {
//no search, show all results
document.documentElement.removeAttribute(searchAttr);
document.querySelectorAll("nav > ul > li:not(.level-hide)").forEach(function(elem) {
elem.style.display = "block";
});
if (typeof hideAllButCurrent === "function"){
//let's do what ever collapse wants to do
hideAllButCurrent();
} else {
//menu by default should be opened
document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) {
elem.style.display = "block";
});
}
} else {
//we are searching
document.documentElement.setAttribute(searchAttr, '');
//show all parents
document.querySelectorAll("nav > ul > li").forEach(function(elem) {
elem.style.display = "block";
});
//hide all results
document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) {
elem.style.display = "none";
});
//show results matching filter
document.querySelectorAll("nav > ul > li > ul a").forEach(function(elem) {
if (!contains(elem.parentNode, search)) {
return;
}
elem.parentNode.style.display = "block";
});
//hide parents without children
document.querySelectorAll("nav > ul > li").forEach(function(parent) {
var countSearchA = 0;
parent.querySelectorAll("a").forEach(function(elem) {
if (contains(elem, search)) {
countSearchA++;
}
});
var countUl = 0;
var countUlVisible = 0;
parent.querySelectorAll("ul").forEach(function(ulP) {
// count all elements that match the search
if (contains(ulP, search)) {
countUl++;
}
// count all visible elements
var children = ulP.children
for (i=0; i<children.length; i++) {
var elem = children[i];
if (elem.style.display != "none") {
countUlVisible++;
}
}
});
if (countSearchA == 0 && countUl === 0){
//has no child at all and does not contain text
parent.style.display = "none";
} else if(countSearchA == 0 && countUlVisible == 0){
//has no visible child and does not contain text
parent.style.display = "none";
}
});
}
});
* {
box-sizing: border-box
}
html, body {
height: 100%;
width: 100%;
}
body {
color: #4d4e53;
background-color: white;
margin: 0 auto;
padding: 0 20px;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-size: 16px;
}
img {
max-width: 100%;
}
a,
a:active {
color: #606;
text-decoration: none;
}
a:hover {
text-decoration: none;
}
article a {
border-bottom: 1px solid #ddd;
}
article a:hover, article a:active {
border-bottom-color: #222;
}
article .description a {
word-break: break-word;
}
p, ul, ol, blockquote {
margin-bottom: 1em;
line-height: 160%;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif;
}
h1, h2, h3, h4, h5, h6 {
color: #000;
font-weight: 400;
margin: 0;
}
h1 {
font-weight: 300;
font-size: 48px;
margin: 1em 0 .5em;
}
h1.page-title {
font-size: 48px;
margin: 1em 30px;
line-height: 100%;
word-wrap: break-word;
}
h2 {
font-size: 24px;
margin: 1.5em 0 .3em;
}
h3 {
font-size: 24px;
margin: 1.2em 0 .3em;
}
h4 {
font-size: 18px;
margin: 1em 0 .2em;
color: #4d4e53;
}
h4.name {
color: #fff;
background: #6d426d;
box-shadow: 0 .25em .5em #d3d3d3;
border-top: 1px solid #d3d3d3;
border-bottom: 1px solid #d3d3d3;
margin: 1.5em 0 0.5em;
padding: .75em 0 .75em 10px;
}
h4.name a {
color: #fc83ff;
}
h4.name a:hover {
border-bottom-color: #fc83ff;
}
h5, .container-overview .subsection-title {
font-size: 120%;
letter-spacing: -0.01em;
margin: 8px 0 3px 0;
}
h6 {
font-size: 100%;
letter-spacing: -0.01em;
margin: 6px 0 3px 0;
font-style: italic;
}
.usertext h1 {
font-family: "Source Sans Pro";
font-size: 24px;
margin: 2.5em 0 1em;
font-weight: 400;
}
.usertext h2 {
font-family: "Source Sans Pro";
font-size: 18px;
margin: 2em 0 0.5em;
font-weight: 400;
}
.usertext h3 {
font-family: "Source Sans Pro";
font-size: 15px;
margin: 1.5em 0 0;
font-weight: 400;
}
.usertext h4 {
font-family: "Source Sans Pro";
font-size: 14px;
margin: 0 0 0;
font-weight: 400;
}
.usertext h5 {
font-size: 12px;
margin: 1em 0 0;
font-weight: normal;
color: #666;
}
.usertext h6 {
font-size: 11px;
margin: 1em 0 0;
font-weight: normal;
font-style: normal;
color: #666;
}
tt, code, kbd, samp {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
background: #f4f4f4;
padding: 1px 5px;
}
.class-description {
font-size: 130%;
line-height: 140%;
margin-bottom: 1em;
margin-top: 1em;
}
.class-description:empty {
margin: 0
}
#main {
float: right;
width: calc(100% - 240px);
}
header {
display: block
}
section {
display: block;
background-color: #fff;
padding: 0 0 0 30px;
}
.variation {
display: none
}
.signature-attributes {
font-size: 60%;
color: #eee;
font-style: italic;
font-weight: lighter;
}
nav {
float: left;
display: block;
width: 250px;
background: #fff;
overflow: auto;
position: fixed;
height: 100%;
}
nav #nav-search{
width: 210px;
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
margin-right: 20px;
margin-top: 20px;
}
nav.wrap a{
word-wrap: break-word;
}
nav h3 {
margin-top: 12px;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 700;
line-height: 24px;
margin: 15px 0 10px;
padding: 0;
color: #000;
}
nav ul {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
font-size: 100%;
line-height: 17px;
padding: 0;
margin: 0;
list-style-type: none;
}
nav ul a,
nav ul a:active {
font-family: 'Montserrat', sans-serif;
line-height: 18px;
padding: 0;
display: block;
font-size: 12px;
}
nav a:hover,
nav a:active {
color: #606;
}
nav > ul {
padding: 0 10px;
}
nav > ul > li > a {
color: #606;
margin-top: 10px;
}
nav ul ul a {
color: hsl(207, 1%, 60%);
border-left: 1px solid hsl(207, 10%, 86%);
}
nav ul ul a,
nav ul ul a:active {
padding-left: 20px
}
nav h2 {
font-size: 13px;
margin: 10px 0 0 0;
padding: 0;
}
nav > h2 > a {
margin: 10px 0 -10px;
color: #606 !important;
}
footer {
color: hsl(0, 0%, 28%);
margin-left: 250px;
display: block;
padding: 15px;
font-style: italic;
font-size: 90%;
}
.ancestors {
color: #999
}
.ancestors a {
color: #999 !important;
}
.clear {
clear: both
}
.important {
font-weight: bold;
color: #950B02;
}
.yes-def {
text-indent: -1000px
}
.type-signature {
color: #CA79CA
}
.type-signature:last-child {
color: #eee;
}
.name, .signature {
font-family: Consolas, Monaco, 'Andale Mono', monospace
}
.signature {
color: #fc83ff;
}
.details {
margin-top: 6px;
border-left: 2px solid #DDD;
line-height: 20px;
font-size: 14px;
}
.details dt {
width: auto;
float: left;
padding-left: 10px;
}
.details dd {
margin-left: 70px;
margin-top: 6px;
margin-bottom: 6px;
}
.details ul {
margin: 0
}
.details ul {
list-style-type: none
}
.details pre.prettyprint {
margin: 0
}
.details .object-value {
padding-top: 0
}
.description {
margin-bottom: 1em;
margin-top: 1em;
}
.code-caption {
font-style: italic;
font-size: 107%;
margin: 0;
}
.prettyprint {
font-size: 14px;
overflow: auto;
}
.prettyprint.source {
width: inherit;
line-height: 18px;
display: block;
background-color: #0d152a;
color: #aeaeae;
}
.prettyprint code {
line-height: 18px;
display: block;
background-color: #0d152a;
color: #4D4E53;
}
.prettyprint > code {
padding: 15px;
}
.prettyprint .linenums code {
padding: 0 15px
}
.prettyprint .linenums li:first-of-type code {
padding-top: 15px
}
.prettyprint code span.line {
display: inline-block
}
.prettyprint.linenums {
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.prettyprint.linenums ol {
padding-left: 0
}
.prettyprint.linenums li {
border-left: 3px #34446B solid;
}
.prettyprint.linenums li.selected, .prettyprint.linenums li.selected * {
background-color: #34446B;
}
.prettyprint.linenums li * {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.prettyprint.linenums li code:empty:after {
content:"";
display:inline-block;
width:0px;
}
table {
border-spacing: 0;
border: 1px solid #ddd;
border-collapse: collapse;
border-radius: 3px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
width: 100%;
font-size: 14px;
margin: 1em 0;
}
td, th {
margin: 0px;
text-align: left;
vertical-align: top;
padding: 10px;
display: table-cell;
}
thead tr, thead tr {
background-color: #fff;
font-weight: bold;
border-bottom: 1px solid #ddd;
}
.params .type {
white-space: nowrap;
}
.params code {
white-space: pre;
}
.params td, .params .name, .props .name, .name code {
color: #4D4E53;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 100%;
}
.params td {
border-top: 1px solid #eee
}
.params td.description > p:first-child, .props td.description > p:first-child {
margin-top: 0;
padding-top: 0;
}
.params td.description > p:last-child, .props td.description > p:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
span.param-type, .params td .param-type, .param-type dd {
color: #606;
font-family: Consolas, Monaco, 'Andale Mono', monospace
}
.param-type dt, .param-type dd {
display: inline-block
}
.param-type {
margin: 14px 0;
}
.disabled {
color: #454545
}
/* navicon button */
.navicon-button {
display: none;
position: relative;
padding: 2.0625rem 1.5rem;
transition: 0.25s;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
opacity: .8;
}
.navicon-button .navicon:before, .navicon-button .navicon:after {
transition: 0.25s;
}
.navicon-button:hover {
transition: 0.5s;
opacity: 1;
}
.navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after {
transition: 0.25s;
}
.navicon-button:hover .navicon:before {
top: .825rem;
}
.navicon-button:hover .navicon:after {
top: -.825rem;
}
/* navicon */
.navicon {
position: relative;
width: 2.5em;
height: .3125rem;
background: #000;
transition: 0.3s;
border-radius: 2.5rem;
}
.navicon:before, .navicon:after {
display: block;
content: "";
height: .3125rem;
width: 2.5rem;
background: #000;
position: absolute;
z-index: -1;
transition: 0.3s 0.25s;
border-radius: 1rem;
}
.navicon:before {
top: .625rem;
}
.navicon:after {
top: -.625rem;
}
/* open */
.nav-trigger:checked + label:not(.steps) .navicon:before,
.nav-trigger:checked + label:not(.steps) .navicon:after {
top: 0 !important;
}
.nav-trigger:checked + label .navicon:before,
.nav-trigger:checked + label .navicon:after {
transition: 0.5s;
}
/* Minus */
.nav-trigger:checked + label {
-webkit-transform: scale(0.75);
transform: scale(0.75);
}
/* × and + */
.nav-trigger:checked + label.plus .navicon,
.nav-trigger:checked + label.x .navicon {
background: transparent;
}
.nav-trigger:checked + label.plus .navicon:before,
.nav-trigger:checked + label.x .navicon:before {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
background: #FFF;
}
.nav-trigger:checked + label.plus .navicon:after,
.nav-trigger:checked + label.x .navicon:after {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFF;
}
.nav-trigger:checked + label.plus {
-webkit-transform: scale(0.75) rotate(45deg);
transform: scale(0.75) rotate(45deg);
}
.nav-trigger:checked ~ nav {
left: 0 !important;
}
.nav-trigger:checked ~ .overlay {
display: block;
}
.nav-trigger {
position: fixed;
top: 0;
clip: rect(0, 0, 0, 0);
}
.overlay {
display: none;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
background: hsla(0, 0%, 0%, 0.5);
z-index: 1;
}
/* nav level */
.level-hide {
display: none;
}
html[data-search-mode] .level-hide {
display: block;
}
@media only screen and (max-width: 680px) {
body {
overflow-x: hidden;
}
nav {
background: #FFF;
width: 250px;
height: 100%;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: -250px;
z-index: 3;
padding: 0 10px;
transition: left 0.2s;
}
.navicon-button {
display: inline-block;
position: fixed;
top: 1.5em;
right: 0;
z-index: 2;
}
#main {
width: 100%;
}
#main h1.page-title {
margin: 1em 0;
}
#main section {
padding: 0;
}
footer {
margin-left: 0;
}
}
/** Add a '#' to static members */
[data-type="member"] a::before {
content: '#';
display: inline-block;
margin-left: -14px;
margin-right: 5px;
}
#disqus_thread{
margin-left: 30px;
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-weight: 400;
src: url('../fonts/Montserrat/Montserrat-Regular.eot'); /* IE9 Compat Modes */
src: url('../fonts/Montserrat/Montserrat-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('../fonts/Montserrat/Montserrat-Regular.woff2') format('woff2'), /* Super Modern Browsers */
url('../fonts/Montserrat/Montserrat-Regular.woff') format('woff'), /* Pretty Modern Browsers */
url('../fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); /* Safari, Android, iOS */
}
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-weight: 700;
src: url('../fonts/Montserrat/Montserrat-Bold.eot'); /* IE9 Compat Modes */
src: url('../fonts/Montserrat/Montserrat-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('../fonts/Montserrat/Montserrat-Bold.woff2') format('woff2'), /* Super Modern Browsers */
url('../fonts/Montserrat/Montserrat-Bold.woff') format('woff'), /* Pretty Modern Browsers */
url('../fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); /* Safari, Android, iOS */
}
@font-face {
font-family: 'Source Sans Pro';
src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot');
src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2') format('woff2'),
url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff') format('woff'),
url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf') format('truetype'),
url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg#source_sans_proregular') format('svg');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot');
src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2') format('woff2'),
url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff') format('woff'),
url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf') format('truetype'),
url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg');
font-weight: 300;
font-style: normal;
}
.pln {
color: #ddd;
}
/* string content */
.str {
color: #61ce3c;
}
/* a keyword */
.kwd {
color: #fbde2d;
}
/* a comment */
.com {
color: #aeaeae;
}
/* a type name */
.typ {
color: #8da6ce;
}
/* a literal value */
.lit {
color: #fbde2d;
}
/* punctuation */
.pun {
color: #ddd;
}
/* lisp open bracket */
.opn {
color: #000000;
}
/* lisp close bracket */
.clo {
color: #000000;
}
/* a markup tag name */
.tag {
color: #8da6ce;
}
/* a markup attribute name */
.atn {
color: #fbde2d;
}
/* a markup attribute value */
.atv {
color: #ddd;
}
/* a declaration */
.dec {
color: #EF5050;
}
/* a variable name */
.var {
color: #c82829;
}
/* a function name */
.fun {
color: #4271ae;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}

Sorry, the diff of this file is too big to display

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>util/precision.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WS2Manager.html">WS2Manager</a><ul class='methods'><li data-type='method'><a href="WS2Manager.html#.getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WS2Manager.html#auth">auth</a></li><li data-type='method'><a href="WS2Manager.html#close">close</a></li><li data-type='method'><a href="WS2Manager.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#getAuthenticatedSocket">getAuthenticatedSocket</a></li><li data-type='method'><a href="WS2Manager.html#getFreeDataSocket">getFreeDataSocket</a></li><li data-type='method'><a href="WS2Manager.html#getNumSockets">getNumSockets</a></li><li data-type='method'><a href="WS2Manager.html#getSocket">getSocket</a></li><li data-type='method'><a href="WS2Manager.html#getSocketInfo">getSocketInfo</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithChannel">getSocketWithChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithDataChannel">getSocketWithDataChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithSubRef">getSocketWithSubRef</a></li><li data-type='method'><a href="WS2Manager.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#onCandle">onCandle</a></li><li data-type='method'><a href="WS2Manager.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#onTicker">onTicker</a></li><li data-type='method'><a href="WS2Manager.html#onTrades">onTrades</a></li><li data-type='method'><a href="WS2Manager.html#openSocket">openSocket</a></li><li data-type='method'><a href="WS2Manager.html#reconnect">reconnect</a></li><li data-type='method'><a href="WS2Manager.html#setAuthArgs">setAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#subscribe">subscribe</a></li><li data-type='method'><a href="WS2Manager.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WS2Manager.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WS2Manager.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#withAllSockets">withAllSockets</a></li></ul></li><li><a href="WSv2.html">WSv2</a><ul class='methods'><li data-type='method'><a href="WSv2.html#auth">auth</a></li><li data-type='method'><a href="WSv2.html#cancelOrder">cancelOrder</a></li><li data-type='method'><a href="WSv2.html#cancelOrders">cancelOrders</a></li><li data-type='method'><a href="WSv2.html#close">close</a></li><li data-type='method'><a href="WSv2.html#enableFlag">enableFlag</a></li><li data-type='method'><a href="WSv2.html#enableSequencing">enableSequencing</a></li><li data-type='method'><a href="WSv2.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WSv2.html#getCandles">getCandles</a></li><li data-type='method'><a href="WSv2.html#getChannelData">getChannelData</a></li><li data-type='method'><a href="WSv2.html#getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WSv2.html#getDataChannelId">getDataChannelId</a></li><li data-type='method'><a href="WSv2.html#getLosslessOB">getLosslessOB</a></li><li data-type='method'><a href="WSv2.html#getOB">getOB</a></li><li data-type='method'><a href="WSv2.html#getURL">getURL</a></li><li data-type='method'><a href="WSv2.html#hasChannel">hasChannel</a></li><li data-type='method'><a href="WSv2.html#hasDataChannel">hasDataChannel</a></li><li data-type='method'><a href="WSv2.html#hasSubscriptionRef">hasSubscriptionRef</a></li><li data-type='method'><a href="WSv2.html#isAuthenticated">isAuthenticated</a></li><li data-type='method'><a href="WSv2.html#isFlagEnabled">isFlagEnabled</a></li><li data-type='method'><a href="WSv2.html#isOpen">isOpen</a></li><li data-type='method'><a href="WSv2.html#isReconnecting">isReconnecting</a></li><li data-type='method'><a href="WSv2.html#managedSubscribe">managedSubscribe</a></li><li data-type='method'><a href="WSv2.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WSv2.html#notifyUI">notifyUI</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeEntry">onAccountTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeUpdate">onAccountTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onBalanceInfoUpdate">onBalanceInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onCandle">onCandle</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditClose">onFundingCreditClose</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditNew">onFundingCreditNew</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditSnapshot">onFundingCreditSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditUpdate">onFundingCreditUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingInfoUpdate">onFundingInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanClose">onFundingLoanClose</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanNew">onFundingLoanNew</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanSnapshot">onFundingLoanSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanUpdate">onFundingLoanUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferClose">onFundingOfferClose</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferNew">onFundingOfferNew</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferSnapshot">onFundingOfferSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferUpdate">onFundingOfferUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeEntry">onFundingTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeUpdate">onFundingTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onInfoMessage">onInfoMessage</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceEnd">onMaintenanceEnd</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceStart">onMaintenanceStart</a></li><li data-type='method'><a href="WSv2.html#onMarginInfoUpdate">onMarginInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onMessage">onMessage</a></li><li data-type='method'><a href="WSv2.html#onNotification">onNotification</a></li><li data-type='method'><a href="WSv2.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WSv2.html#onOrderBookChecksum">onOrderBookChecksum</a></li><li data-type='method'><a href="WSv2.html#onOrderClose">onOrderClose</a></li><li data-type='method'><a href="WSv2.html#onOrderNew">onOrderNew</a></li><li data-type='method'><a href="WSv2.html#onOrderSnapshot">onOrderSnapshot</a></li><li data-type='method'><a href="WSv2.html#onOrderUpdate">onOrderUpdate</a></li><li data-type='method'><a href="WSv2.html#onPositionClose">onPositionClose</a></li><li data-type='method'><a href="WSv2.html#onPositionNew">onPositionNew</a></li><li data-type='method'><a href="WSv2.html#onPositionSnapshot">onPositionSnapshot</a></li><li data-type='method'><a href="WSv2.html#onPositionUpdate">onPositionUpdate</a></li><li data-type='method'><a href="WSv2.html#onServerRestart">onServerRestart</a></li><li data-type='method'><a href="WSv2.html#onStatus">onStatus</a></li><li data-type='method'><a href="WSv2.html#onTicker">onTicker</a></li><li data-type='method'><a href="WSv2.html#onTradeEntry">onTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onTrades">onTrades</a></li><li data-type='method'><a href="WSv2.html#onWalletSnapshot">onWalletSnapshot</a></li><li data-type='method'><a href="WSv2.html#onWalletUpdate">onWalletUpdate</a></li><li data-type='method'><a href="WSv2.html#open">open</a></li><li data-type='method'><a href="WSv2.html#reconnect">reconnect</a></li><li data-type='method'><a href="WSv2.html#removeListeners">removeListeners</a></li><li data-type='method'><a href="WSv2.html#requestCalc">requestCalc</a></li><li data-type='method'><a href="WSv2.html#send">send</a></li><li data-type='method'><a href="WSv2.html#sequencingEnabled">sequencingEnabled</a></li><li data-type='method'><a href="WSv2.html#setAPICredentials">setAPICredentials</a></li><li data-type='method'><a href="WSv2.html#submitOrder">submitOrder</a></li><li data-type='method'><a href="WSv2.html#submitOrderMultiOp">submitOrderMultiOp</a></li><li data-type='method'><a href="WSv2.html#subscribe">subscribe</a></li><li data-type='method'><a href="WSv2.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WSv2.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#subscribeStatus">subscribeStatus</a></li><li data-type='method'><a href="WSv2.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WSv2.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WSv2.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WSv2.html#unsubscribeCandles">unsubscribeCandles</a></li><li data-type='method'><a href="WSv2.html#unsubscribeOrderBook">unsubscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#unsubscribeStatus">unsubscribeStatus</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTicker">unsubscribeTicker</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTrades">unsubscribeTrades</a></li><li data-type='method'><a href="WSv2.html#updateAuthArgs">updateAuthArgs</a></li><li data-type='method'><a href="WSv2.html#updateOrder">updateOrder</a></li><li data-type='method'><a href="WSv2.html#usesAgent">usesAgent</a></li></ul></li><li></li></ul><h3>Global</h3><ul><li><a href="global.html#setSigFig">setSigFig</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">util/precision.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const Big = require('bignumber.js')
const DEFAULT_SIG_FIGS = 5
const PRICE_SIG_FIGS = 5
const AMOUNT_DECIMALS = 8
/**
* Smartly set the precision (decimal) on a value based off of the significant
* digit maximum. For example, calling with 3.34 when the max sig figs allowed
* is 5 would return '3.3400', the representation number of decimals IF they
* weren't zeros.
*
* @param {number} number - number to manipulate
* @param {number} [maxSigs] - default 5
* @returns {string} str
*/
const setSigFig = (number = 0, maxSigs = DEFAULT_SIG_FIGS) => {
const n = +(number)
if (!isFinite(n)) {
return number
}
const value = n.toPrecision(maxSigs)
return /e/.test(value)
? new Big(value).toString()
: value
}
const setPrecision = (number = 0, decimals = 0) => {
const n = +(number)
return (isFinite(n))
? n.toFixed(decimals)
: number
}
const prepareAmount = (amount = 0) => {
return setPrecision(amount, AMOUNT_DECIMALS)
}
const preparePrice = (price = 0) => {
return setSigFig(price, PRICE_SIG_FIGS)
}
module.exports = {
setSigFig, setPrecision, prepareAmount, preparePrice
}
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.6</a> on Mon Jan 25 2021 16:36:37 GMT+0100 (Central European Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>util/ws2.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WS2Manager.html">WS2Manager</a><ul class='methods'><li data-type='method'><a href="WS2Manager.html#.getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WS2Manager.html#auth">auth</a></li><li data-type='method'><a href="WS2Manager.html#close">close</a></li><li data-type='method'><a href="WS2Manager.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#getAuthenticatedSocket">getAuthenticatedSocket</a></li><li data-type='method'><a href="WS2Manager.html#getFreeDataSocket">getFreeDataSocket</a></li><li data-type='method'><a href="WS2Manager.html#getNumSockets">getNumSockets</a></li><li data-type='method'><a href="WS2Manager.html#getSocket">getSocket</a></li><li data-type='method'><a href="WS2Manager.html#getSocketInfo">getSocketInfo</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithChannel">getSocketWithChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithDataChannel">getSocketWithDataChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithSubRef">getSocketWithSubRef</a></li><li data-type='method'><a href="WS2Manager.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#onCandle">onCandle</a></li><li data-type='method'><a href="WS2Manager.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#onTicker">onTicker</a></li><li data-type='method'><a href="WS2Manager.html#onTrades">onTrades</a></li><li data-type='method'><a href="WS2Manager.html#openSocket">openSocket</a></li><li data-type='method'><a href="WS2Manager.html#reconnect">reconnect</a></li><li data-type='method'><a href="WS2Manager.html#setAuthArgs">setAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#subscribe">subscribe</a></li><li data-type='method'><a href="WS2Manager.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WS2Manager.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WS2Manager.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#withAllSockets">withAllSockets</a></li></ul></li><li><a href="WSv2.html">WSv2</a><ul class='methods'><li data-type='method'><a href="WSv2.html#auth">auth</a></li><li data-type='method'><a href="WSv2.html#cancelOrder">cancelOrder</a></li><li data-type='method'><a href="WSv2.html#cancelOrders">cancelOrders</a></li><li data-type='method'><a href="WSv2.html#close">close</a></li><li data-type='method'><a href="WSv2.html#enableFlag">enableFlag</a></li><li data-type='method'><a href="WSv2.html#enableSequencing">enableSequencing</a></li><li data-type='method'><a href="WSv2.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WSv2.html#getCandles">getCandles</a></li><li data-type='method'><a href="WSv2.html#getChannelData">getChannelData</a></li><li data-type='method'><a href="WSv2.html#getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WSv2.html#getDataChannelId">getDataChannelId</a></li><li data-type='method'><a href="WSv2.html#getLosslessOB">getLosslessOB</a></li><li data-type='method'><a href="WSv2.html#getOB">getOB</a></li><li data-type='method'><a href="WSv2.html#getURL">getURL</a></li><li data-type='method'><a href="WSv2.html#hasChannel">hasChannel</a></li><li data-type='method'><a href="WSv2.html#hasDataChannel">hasDataChannel</a></li><li data-type='method'><a href="WSv2.html#hasSubscriptionRef">hasSubscriptionRef</a></li><li data-type='method'><a href="WSv2.html#isAuthenticated">isAuthenticated</a></li><li data-type='method'><a href="WSv2.html#isFlagEnabled">isFlagEnabled</a></li><li data-type='method'><a href="WSv2.html#isOpen">isOpen</a></li><li data-type='method'><a href="WSv2.html#isReconnecting">isReconnecting</a></li><li data-type='method'><a href="WSv2.html#managedSubscribe">managedSubscribe</a></li><li data-type='method'><a href="WSv2.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WSv2.html#notifyUI">notifyUI</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeEntry">onAccountTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeUpdate">onAccountTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onBalanceInfoUpdate">onBalanceInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onCandle">onCandle</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditClose">onFundingCreditClose</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditNew">onFundingCreditNew</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditSnapshot">onFundingCreditSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditUpdate">onFundingCreditUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingInfoUpdate">onFundingInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanClose">onFundingLoanClose</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanNew">onFundingLoanNew</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanSnapshot">onFundingLoanSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanUpdate">onFundingLoanUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferClose">onFundingOfferClose</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferNew">onFundingOfferNew</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferSnapshot">onFundingOfferSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferUpdate">onFundingOfferUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeEntry">onFundingTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeUpdate">onFundingTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onInfoMessage">onInfoMessage</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceEnd">onMaintenanceEnd</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceStart">onMaintenanceStart</a></li><li data-type='method'><a href="WSv2.html#onMarginInfoUpdate">onMarginInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onMessage">onMessage</a></li><li data-type='method'><a href="WSv2.html#onNotification">onNotification</a></li><li data-type='method'><a href="WSv2.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WSv2.html#onOrderBookChecksum">onOrderBookChecksum</a></li><li data-type='method'><a href="WSv2.html#onOrderClose">onOrderClose</a></li><li data-type='method'><a href="WSv2.html#onOrderNew">onOrderNew</a></li><li data-type='method'><a href="WSv2.html#onOrderSnapshot">onOrderSnapshot</a></li><li data-type='method'><a href="WSv2.html#onOrderUpdate">onOrderUpdate</a></li><li data-type='method'><a href="WSv2.html#onPositionClose">onPositionClose</a></li><li data-type='method'><a href="WSv2.html#onPositionNew">onPositionNew</a></li><li data-type='method'><a href="WSv2.html#onPositionSnapshot">onPositionSnapshot</a></li><li data-type='method'><a href="WSv2.html#onPositionUpdate">onPositionUpdate</a></li><li data-type='method'><a href="WSv2.html#onServerRestart">onServerRestart</a></li><li data-type='method'><a href="WSv2.html#onStatus">onStatus</a></li><li data-type='method'><a href="WSv2.html#onTicker">onTicker</a></li><li data-type='method'><a href="WSv2.html#onTradeEntry">onTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onTrades">onTrades</a></li><li data-type='method'><a href="WSv2.html#onWalletSnapshot">onWalletSnapshot</a></li><li data-type='method'><a href="WSv2.html#onWalletUpdate">onWalletUpdate</a></li><li data-type='method'><a href="WSv2.html#open">open</a></li><li data-type='method'><a href="WSv2.html#reconnect">reconnect</a></li><li data-type='method'><a href="WSv2.html#removeListeners">removeListeners</a></li><li data-type='method'><a href="WSv2.html#requestCalc">requestCalc</a></li><li data-type='method'><a href="WSv2.html#send">send</a></li><li data-type='method'><a href="WSv2.html#sequencingEnabled">sequencingEnabled</a></li><li data-type='method'><a href="WSv2.html#setAPICredentials">setAPICredentials</a></li><li data-type='method'><a href="WSv2.html#submitOrder">submitOrder</a></li><li data-type='method'><a href="WSv2.html#submitOrderMultiOp">submitOrderMultiOp</a></li><li data-type='method'><a href="WSv2.html#subscribe">subscribe</a></li><li data-type='method'><a href="WSv2.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WSv2.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#subscribeStatus">subscribeStatus</a></li><li data-type='method'><a href="WSv2.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WSv2.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WSv2.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WSv2.html#unsubscribeCandles">unsubscribeCandles</a></li><li data-type='method'><a href="WSv2.html#unsubscribeOrderBook">unsubscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#unsubscribeStatus">unsubscribeStatus</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTicker">unsubscribeTicker</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTrades">unsubscribeTrades</a></li><li data-type='method'><a href="WSv2.html#updateAuthArgs">updateAuthArgs</a></li><li data-type='method'><a href="WSv2.html#updateOrder">updateOrder</a></li><li data-type='method'><a href="WSv2.html#usesAgent">usesAgent</a></li></ul></li><li></li></ul><h3>Global</h3><ul><li><a href="global.html#setSigFig">setSigFig</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">util/ws2.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>'use strict'
const _findLast = require('lodash/findLast')
/**
* Resolves the message payload; useful for getting around sequence numbers
*
* @param {Array} msg - message to parse
* @returns {Array} payload - undefined if not found
*/
module.exports = (msg = []) => {
return _findLast(msg, i => Array.isArray(i))
}
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.6</a> on Mon Jan 25 2021 16:36:37 GMT+0100 (Central European Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ws2_manager.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WS2Manager.html">WS2Manager</a><ul class='methods'><li data-type='method'><a href="WS2Manager.html#.getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WS2Manager.html#auth">auth</a></li><li data-type='method'><a href="WS2Manager.html#close">close</a></li><li data-type='method'><a href="WS2Manager.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#getAuthenticatedSocket">getAuthenticatedSocket</a></li><li data-type='method'><a href="WS2Manager.html#getFreeDataSocket">getFreeDataSocket</a></li><li data-type='method'><a href="WS2Manager.html#getNumSockets">getNumSockets</a></li><li data-type='method'><a href="WS2Manager.html#getSocket">getSocket</a></li><li data-type='method'><a href="WS2Manager.html#getSocketInfo">getSocketInfo</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithChannel">getSocketWithChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithDataChannel">getSocketWithDataChannel</a></li><li data-type='method'><a href="WS2Manager.html#getSocketWithSubRef">getSocketWithSubRef</a></li><li data-type='method'><a href="WS2Manager.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#onCandle">onCandle</a></li><li data-type='method'><a href="WS2Manager.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#onTicker">onTicker</a></li><li data-type='method'><a href="WS2Manager.html#onTrades">onTrades</a></li><li data-type='method'><a href="WS2Manager.html#openSocket">openSocket</a></li><li data-type='method'><a href="WS2Manager.html#reconnect">reconnect</a></li><li data-type='method'><a href="WS2Manager.html#setAuthArgs">setAuthArgs</a></li><li data-type='method'><a href="WS2Manager.html#subscribe">subscribe</a></li><li data-type='method'><a href="WS2Manager.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WS2Manager.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WS2Manager.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WS2Manager.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WS2Manager.html#withAllSockets">withAllSockets</a></li></ul></li><li><a href="WSv2.html">WSv2</a><ul class='methods'><li data-type='method'><a href="WSv2.html#auth">auth</a></li><li data-type='method'><a href="WSv2.html#cancelOrder">cancelOrder</a></li><li data-type='method'><a href="WSv2.html#cancelOrders">cancelOrders</a></li><li data-type='method'><a href="WSv2.html#close">close</a></li><li data-type='method'><a href="WSv2.html#enableFlag">enableFlag</a></li><li data-type='method'><a href="WSv2.html#enableSequencing">enableSequencing</a></li><li data-type='method'><a href="WSv2.html#getAuthArgs">getAuthArgs</a></li><li data-type='method'><a href="WSv2.html#getCandles">getCandles</a></li><li data-type='method'><a href="WSv2.html#getChannelData">getChannelData</a></li><li data-type='method'><a href="WSv2.html#getDataChannelCount">getDataChannelCount</a></li><li data-type='method'><a href="WSv2.html#getDataChannelId">getDataChannelId</a></li><li data-type='method'><a href="WSv2.html#getLosslessOB">getLosslessOB</a></li><li data-type='method'><a href="WSv2.html#getOB">getOB</a></li><li data-type='method'><a href="WSv2.html#getURL">getURL</a></li><li data-type='method'><a href="WSv2.html#hasChannel">hasChannel</a></li><li data-type='method'><a href="WSv2.html#hasDataChannel">hasDataChannel</a></li><li data-type='method'><a href="WSv2.html#hasSubscriptionRef">hasSubscriptionRef</a></li><li data-type='method'><a href="WSv2.html#isAuthenticated">isAuthenticated</a></li><li data-type='method'><a href="WSv2.html#isFlagEnabled">isFlagEnabled</a></li><li data-type='method'><a href="WSv2.html#isOpen">isOpen</a></li><li data-type='method'><a href="WSv2.html#isReconnecting">isReconnecting</a></li><li data-type='method'><a href="WSv2.html#managedSubscribe">managedSubscribe</a></li><li data-type='method'><a href="WSv2.html#managedUnsubscribe">managedUnsubscribe</a></li><li data-type='method'><a href="WSv2.html#notifyUI">notifyUI</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeEntry">onAccountTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onAccountTradeUpdate">onAccountTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onBalanceInfoUpdate">onBalanceInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onCandle">onCandle</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditClose">onFundingCreditClose</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditNew">onFundingCreditNew</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditSnapshot">onFundingCreditSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingCreditUpdate">onFundingCreditUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingInfoUpdate">onFundingInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanClose">onFundingLoanClose</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanNew">onFundingLoanNew</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanSnapshot">onFundingLoanSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingLoanUpdate">onFundingLoanUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferClose">onFundingOfferClose</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferNew">onFundingOfferNew</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferSnapshot">onFundingOfferSnapshot</a></li><li data-type='method'><a href="WSv2.html#onFundingOfferUpdate">onFundingOfferUpdate</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeEntry">onFundingTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onFundingTradeUpdate">onFundingTradeUpdate</a></li><li data-type='method'><a href="WSv2.html#onInfoMessage">onInfoMessage</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceEnd">onMaintenanceEnd</a></li><li data-type='method'><a href="WSv2.html#onMaintenanceStart">onMaintenanceStart</a></li><li data-type='method'><a href="WSv2.html#onMarginInfoUpdate">onMarginInfoUpdate</a></li><li data-type='method'><a href="WSv2.html#onMessage">onMessage</a></li><li data-type='method'><a href="WSv2.html#onNotification">onNotification</a></li><li data-type='method'><a href="WSv2.html#onOrderBook">onOrderBook</a></li><li data-type='method'><a href="WSv2.html#onOrderBookChecksum">onOrderBookChecksum</a></li><li data-type='method'><a href="WSv2.html#onOrderClose">onOrderClose</a></li><li data-type='method'><a href="WSv2.html#onOrderNew">onOrderNew</a></li><li data-type='method'><a href="WSv2.html#onOrderSnapshot">onOrderSnapshot</a></li><li data-type='method'><a href="WSv2.html#onOrderUpdate">onOrderUpdate</a></li><li data-type='method'><a href="WSv2.html#onPositionClose">onPositionClose</a></li><li data-type='method'><a href="WSv2.html#onPositionNew">onPositionNew</a></li><li data-type='method'><a href="WSv2.html#onPositionSnapshot">onPositionSnapshot</a></li><li data-type='method'><a href="WSv2.html#onPositionUpdate">onPositionUpdate</a></li><li data-type='method'><a href="WSv2.html#onServerRestart">onServerRestart</a></li><li data-type='method'><a href="WSv2.html#onStatus">onStatus</a></li><li data-type='method'><a href="WSv2.html#onTicker">onTicker</a></li><li data-type='method'><a href="WSv2.html#onTradeEntry">onTradeEntry</a></li><li data-type='method'><a href="WSv2.html#onTrades">onTrades</a></li><li data-type='method'><a href="WSv2.html#onWalletSnapshot">onWalletSnapshot</a></li><li data-type='method'><a href="WSv2.html#onWalletUpdate">onWalletUpdate</a></li><li data-type='method'><a href="WSv2.html#open">open</a></li><li data-type='method'><a href="WSv2.html#reconnect">reconnect</a></li><li data-type='method'><a href="WSv2.html#removeListeners">removeListeners</a></li><li data-type='method'><a href="WSv2.html#requestCalc">requestCalc</a></li><li data-type='method'><a href="WSv2.html#send">send</a></li><li data-type='method'><a href="WSv2.html#sequencingEnabled">sequencingEnabled</a></li><li data-type='method'><a href="WSv2.html#setAPICredentials">setAPICredentials</a></li><li data-type='method'><a href="WSv2.html#submitOrder">submitOrder</a></li><li data-type='method'><a href="WSv2.html#submitOrderMultiOp">submitOrderMultiOp</a></li><li data-type='method'><a href="WSv2.html#subscribe">subscribe</a></li><li data-type='method'><a href="WSv2.html#subscribeCandles">subscribeCandles</a></li><li data-type='method'><a href="WSv2.html#subscribeOrderBook">subscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#subscribeStatus">subscribeStatus</a></li><li data-type='method'><a href="WSv2.html#subscribeTicker">subscribeTicker</a></li><li data-type='method'><a href="WSv2.html#subscribeTrades">subscribeTrades</a></li><li data-type='method'><a href="WSv2.html#unsubscribe">unsubscribe</a></li><li data-type='method'><a href="WSv2.html#unsubscribeCandles">unsubscribeCandles</a></li><li data-type='method'><a href="WSv2.html#unsubscribeOrderBook">unsubscribeOrderBook</a></li><li data-type='method'><a href="WSv2.html#unsubscribeStatus">unsubscribeStatus</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTicker">unsubscribeTicker</a></li><li data-type='method'><a href="WSv2.html#unsubscribeTrades">unsubscribeTrades</a></li><li data-type='method'><a href="WSv2.html#updateAuthArgs">updateAuthArgs</a></li><li data-type='method'><a href="WSv2.html#updateOrder">updateOrder</a></li><li data-type='method'><a href="WSv2.html#usesAgent">usesAgent</a></li></ul></li><li></li></ul><h3>Global</h3><ul><li><a href="global.html#setSigFig">setSigFig</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">ws2_manager.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>'use strict'
const { EventEmitter } = require('events')
const debug = require('debug')('bfx:ws2:manager')
const _isEqual = require('lodash/isEqual')
const _isFinite = require('lodash/isFinite')
const _includes = require('lodash/includes')
const _pick = require('lodash/pick')
const Promise = require('bluebird')
const PromiseThrottle = require('promise-throttle')
const WSv2 = require('./transports/ws2')
const DATA_CHANNEL_LIMIT = 30
const reconnectThrottler = new PromiseThrottle({
requestsPerSecond: 10 / 60.0,
promiseImplementation: Promise
})
/**
* Provides a wrapper around the WSv2 class, opening new sockets when a
* subscription would push a single socket over the data channel limit.
*
* For more complex operations, grab a socket reference with getSocket() or
* getFreeDataSocket(), or create a new WSv2 instance manually
*
* @example
* const rest = new RESTv2({ transform: true })
* const details = await rest.symbolDetails()
* const symbols = details.map(d => `t${d.pair.toUpperCase()}`)
* const timeFrames = ['1m', '5m', '30m', '1h', '6h']
* const keys = _flatten(symbols.map(s => {
* return timeFrames.map(tf => `trade:${tf}:${s}`)
* }))
*
* const m = new Manager()
*
* m.on('error', (err) => {
* debug('error: %s', err)
* })
*
* m.once('open', () => {
* debug('open')
*
* keys.forEach(key => {
* m.subscribeCandles(key)
* m.onCandle({ key }, (candles) => {
* debug('recv %d candles on channel %s', candles.length, key)
* })
* })
*
* symbols.forEach(symbol => {
* m.subscribeTrades(symbol)
* m.onTrades({ symbol }, (trades) => {
* debug('recv %d trades on channel %s', trades.length, symbol)
* })
* })
*
* symbols.forEach(symbol => {
* m.subscribeTicker(symbol)
* m.onTicker({ symbol }, (ticker) => {
* debug('recv ticker on channel %s: %j', symbol, ticker)
* })
* })
*
* symbols.forEach(symbol => {
* m.subscribeOrderBook(symbol)
* m.onOrderBook({ symbol }, (update) => {
* debug('recv book update on channel %s: %j', symbol, update)
* })
* })
*
* setInterval(() => {
* debug('num keys: %d', keys.length)
* debug('num sockets: %d', m.getNumSockets())
* debug('socket info: %j', m.getSocketInfo())
* }, 5000)
* })
*
* m.openSocket()
*/
class WS2Manager extends EventEmitter {
/**
* @param {object} socketArgs - passed to WSv2 constructors
* @param {object} [authArgs] - cached for all internal socket auth() calls
* @param {number} [authArgs.calc] - default 0
* @param {number} [authArgs.dms] - default 0
*/
constructor (socketArgs, authArgs = { calc: 0, dms: 0 }) {
super()
this.setMaxListeners(1000)
this._authArgs = authArgs
this._sockets = []
this._socketArgs = {
...(socketArgs || {}),
reconnectThrottler
}
}
/**
* Update authentication arguments on all sockets
*
* @param {object} args - arguments
* @param {number} [args.calc] - calc value
* @param {number} [args.dms] - active 4
*/
setAuthArgs (args = {}) {
this._authArgs = {
...this._authArgs,
...args
}
this._sockets.forEach(socket => socket.ws.updateAuthArgs(this._authArgs))
}
/**
* Retrieve internal authentication arguments
*
* @returns {object} args
*/
getAuthArgs () {
return this._authArgs
}
/**
* Reconnects all open sockets
*
* @returns {Promise} p
*/
async reconnect () {
return Promise.all(this._sockets.map(socket => socket.ws.reconnect()))
}
/**
* Closes all open sockets
*
* @returns {Promise} p
*/
async close () {
return Promise.all(this._sockets.map(socket => socket.ws.close()))
}
/**
* @param {object} s - socket state
* @returns {number} count - # of subscribed/pending data channels
*/
static getDataChannelCount (s) {
let count = s.ws.getDataChannelCount()
count += s.pendingSubscriptions.length
count -= s.pendingUnsubscriptions.length
return count
}
/**
* @returns {number} n
*/
getNumSockets () {
return this._sockets.length
}
/**
* @param {number} i - index into pool
* @returns {object} state
*/
getSocket (i) {
return this._sockets[i]
}
/**
* Returns an object which can be logged to inspect the socket pool
*
* @returns {object[]} socketInfo
*/
getSocketInfo () {
return this._sockets.map(s => ({
nChannels: WS2Manager.getDataChannelCount(s)
}))
}
/**
* Authenticates all existing &amp; future sockets with the provided credentials.
* Does nothing if an apiKey/apiSecret pair are already known.
*
* @param {object} args - arguments
* @param {string} args.apiKey - saved if not already provided
* @param {string} args.apiSecret - saved if not already provided
* @param {number} [args.calc] - default 0
* @param {number} [args.dms] - dead man switch, active 4
*/
auth ({ apiKey, apiSecret, calc, dms } = {}) {
if (this._socketArgs.apiKey || this._socketArgs.apiSecret) {
debug('error: auth credentials already provided! refusing auth')
return
}
this._socketArgs.apiKey = apiKey
this._socketArgs.apiSecret = apiSecret
if (_isFinite(calc)) this._authArgs.calc = calc
if (_isFinite(dms)) this._authArgs.dms = dms
this._sockets.forEach(s => {
if (!s.ws.isAuthenticated()) {
s.ws.setAPICredentials(apiKey, apiSecret)
s.ws.updateAuthArgs(this._authArgs)
s.ws.auth()
}
})
}
/**
* Creates a new socket/state instance and adds it to the internal pool. Binds
* event listeners to forward via our own event emitter, and to manage pending
* subs/unsubs.
*
* @returns {object} state
*/
openSocket () {
const { apiKey, apiSecret } = this._socketArgs
const ws = new WSv2(this._socketArgs)
const wsState = {
pendingSubscriptions: [],
pendingUnsubscriptions: [],
ws
}
ws.updateAuthArgs(this._authArgs)
ws.on('open', () => this.emit('open', ws))
ws.on('message', (msg = {}) => this.emit('message', msg, ws))
ws.on('error', (error) => this.emit('error', error, ws))
ws.on('auth', () => this.emit('auth', ws))
ws.on('close', () => this.emit('close', ws))
ws.on('subscribed', (msg = {}) => {
this.emit('subscribed', msg)
const i = wsState.pendingSubscriptions.find(sub => {
const fv = _pick(msg, Object.keys(sub[1]))
return (
(sub[0] === msg.channel) &amp;&amp;
_isEqual(fv, sub[1])
)
})
if (i === -1) {
debug('error removing pending sub: %j', msg)
return
}
wsState.pendingSubscriptions.splice(i, 1)
})
ws.on('unsubscribed', (msg = {}) => {
this.emit('unsubscribed', msg)
const { chanId } = msg
const i = wsState.pendingUnsubscriptions.findIndex(cid => (
cid === `${chanId}`
))
if (i === -1) {
debug('error removing pending unsub: %j', msg)
return
}
wsState.pendingUnsubscriptions.splice(i, 1)
})
if (apiKey &amp;&amp; apiSecret) { // auto-auth
ws.once('open', () => {
debug('authenticating socket...')
ws.auth().then(() => {
return debug('socket authenticated')
}).catch((err) => {
debug('error authenticating socket: %s', err.message)
})
})
}
ws.open().then(() => {
return debug('socket connection opened')
}).catch((err) => {
debug('error opening socket: %s', err.stack)
})
this._sockets.push(wsState)
return wsState
}
/**
* @returns {object} state
*/
getAuthenticatedSocket () {
return this._sockets.find(s => s.ws.isAuthenticated())
}
/**
* Returns the first socket that has less active/pending channels than the
* DATA_CHANNEL_LIMIT
*
* @returns {object} state - undefined if none found
*/
getFreeDataSocket () {
return this._sockets.find(s => (
WS2Manager.getDataChannelCount(s) &lt; DATA_CHANNEL_LIMIT
))
}
/**
* Returns the first socket that is subscribed/pending sub to the specified
* channel.
*
* @param {string} type - i.e. 'book'
* @param {object} filter - i.e. { symbol: 'tBTCUSD', prec: 'R0' }
* @returns {object} wsState - undefined if not found
*/
getSocketWithDataChannel (type, filter) {
return this._sockets.find(s => {
const subI = s.pendingSubscriptions.findIndex(s => (
s[0] === type &amp;&amp; _isEqual(s[1], filter)
))
if (subI !== -1) {
return true
}
// Confirm unsub is not pending
const cid = s.ws.getDataChannelId(type, filter)
if (!cid) {
return false
}
return cid &amp;&amp; !_includes(s.pendingUnsubscriptions, cid)
})
}
/**
* NOTE: Cannot filter against pending subscriptions, due to unknown chanId
*
* @param {number} chanId - channel ID
* @returns {object} wsState - undefined if not found
*/
getSocketWithChannel (chanId) {
return this._sockets.find(s => {
return (
s.ws.hasChannel(chanId) &amp;&amp;
!_includes(s.pendingUnsubscriptions, chanId)
)
})
}
/**
* @param {string} channel - channel type
* @param {string} identifier - unique channel identifier
* @returns {object} wsState - undefined if not found
*/
getSocketWithSubRef (channel, identifier) {
return this._sockets.find(s => s.ws.hasSubscriptionRef(channel, identifier))
}
/**
* Calls the provided cb with all internal socket instances
*
* @param {Function} cb - callback
*/
withAllSockets (cb) {
this._sockets.forEach((ws2) => {
cb(ws2)
})
}
/**
* Subscribes a free data socket if available to the specified channel, or
* opens a new socket &amp; subs if needed.
*
* @param {string} type - i.e. 'book'
* @param {string} ident - i.e. 'tBTCUSD'
* @param {object} filter - i.e. { symbol: 'tBTCUSD', prec: 'R0' }
*/
subscribe (type, ident, filter) {
let s = this.getFreeDataSocket()
if (!s) {
s = this.openSocket()
}
const doSub = () => {
s.ws.managedSubscribe(type, ident, filter)
}
if (!s.ws.isOpen()) {
s.ws.once('open', doSub)
} else {
doSub()
}
s.pendingSubscriptions.push([type, filter])
}
/**
* @param {string} channel - channel type
* @param {string} identifier - unique channel identifier
*/
managedUnsubscribe (channel, identifier) {
const s = this.getSocketWithSubRef(channel, identifier)
if (!s) {
debug('cannot unsub from unknown channel %s: %s', channel, identifier)
return
}
const chanId = s.ws._chanIdByIdentifier(channel, identifier)
s.ws.managedUnsubscribe(channel, identifier)
s.pendingUnsubscriptions.push(chanId)
}
/**
* Unsubscribes the first socket w/ the specified channel. Does nothing if no
* such socket is found.
*
* @param {number} chanId - channel ID
*/
unsubscribe (chanId) {
const s = this.getSocketWithChannel(chanId)
if (!s) {
debug('cannot unsub from unknown channel: %d', chanId)
return
}
s.ws.unsubscribe(chanId)
s.pendingUnsubscriptions.push(chanId)
}
/**
* @param {string} symbol - symbol for ticker
*/
subscribeTicker (symbol) {
this.subscribe('ticker', symbol, { symbol })
}
/**
* @param {string} symbol - symbol for trades
*/
subscribeTrades (symbol) {
this.subscribe('trades', symbol, { symbol })
}
/**
* @param {string} symbol - symbol for order book
* @param {string} [prec] - precision, i.e. 'R0', default 'P0'
* @param {string} [len] - length, default '25'
* @param {string} [freq] - default 'F0'
*/
subscribeOrderBook (symbol, prec = 'P0', len = '25', freq = 'F0') {
const filter = {}
if (symbol) filter.symbol = symbol
if (prec) filter.prec = prec
if (len) filter.len = len
if (freq) filter.freq = freq
this.subscribe('book', symbol, filter)
}
/**
* @param {string} key - candle channel key
*/
subscribeCandles (key) {
this.subscribe('candles', key, { key })
}
/**
* @param {object} opts - options
* @param {string} opts.key - candle set key, i.e. trade:30m:tBTCUSD
* @param {string} [opts.cbGID] - callback group id
* @param {Function} cb - callback
* @throws an error if no data socket is available
* @see https://docs.bitfinex.com/v2/reference#ws-public-candle
*/
onCandle ({ key, cbGID }, cb) {
const s = this.getSocketWithDataChannel('candles', { key })
if (!s) {
throw new Error('no data socket available; did you provide a key?')
}
s.ws.onCandle({ key, cbGID }, cb)
}
/**
* @param {object} opts - options
* @param {string} opts.symbol - order book symbol
* @param {string} [opts.prec] - precision, i.e. 'R0', default 'P0'
* @param {string} [opts.len] - length, default '25'
* @param {string} [opts.freq] - default 'F0'
* @param {string} [opts.cbGID] - callback group id
* @param {Function} cb - callback
* @throws an error if no data socket is available
* @see https://docs.bitfinex.com/v2/reference#ws-public-order-books
*/
onOrderBook ({ symbol, prec = 'P0', len = '25', freq = 'F0', cbGID }, cb) {
const filter = {}
if (symbol) filter.symbol = symbol
if (prec) filter.prec = prec
if (len) filter.len = len
if (freq) filter.freq = freq
const s = this.getSocketWithDataChannel('book', filter)
if (!s) {
throw new Error('no data socket available; did you provide a symbol?')
}
s.ws.onOrderBook({ cbGID, ...filter }, cb)
}
/**
* @param {object} opts - options
* @param {string} [opts.symbol] - symbol for trades
* @param {string} [opts.cbGID] - callback group id
* @param {Function} cb - callback
* @throws an error if no data socket is available
* @see https://docs.bitfinex.com/v2/reference#ws-public-trades
*/
onTrades ({ symbol, cbGID }, cb) {
const s = this.getSocketWithDataChannel('trades', { symbol })
if (!s) {
throw new Error('no data socket available; did you provide a symbol?')
}
s.ws.onTrades({ symbol, cbGID }, cb)
}
/**
* @param {object} opts - options
* @param {string} [opts.symbol] - symbol for ticker
* @param {string} [opts.cbGID] - callback group id
* @param {Function} cb - callback
* @throws an error if no data socket is available
* @see https://docs.bitfinex.com/v2/reference#ws-public-ticker
*/
onTicker ({ symbol = '', cbGID } = {}, cb) {
const s = this.getSocketWithDataChannel('ticker', { symbol })
if (!s) {
throw new Error('no data socket available; did you provide a symbol?')
}
s.ws.onTicker({ symbol, cbGID }, cb)
}
}
module.exports = WS2Manager
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.6</a> on Mon Jan 25 2021 16:36:37 GMT+0100 (Central European Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
</body>
</html>

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

'use strict'
const Promise = require('bluebird')
const _isEmpty = require('lodash/isEmpty')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-claim-positions',
rest: { env: true, transform: true },
readline: true,
params: {
filterByMarket: false
}
}, async ({ debug, debugTable, rest, readline, params }) => {
const { filterByMarket } = params
const allPositions = await rest.positions()
const positions = _isEmpty(filterByMarket)
? allPositions
: allPositions.filter(({ symbol }) => symbol === filterByMarket)
if (positions.length === 0) {
debug('no positions match filter')
return
}
debug(
'found %d open positions on market(s) %s\n', positions.length,
positions.map(({ symbol }) => symbol).join(',')
)
debugTable({
headers: ['Symbol', 'Status', 'Amount', 'Base Price', 'P/L'],
rows: positions.map(p => ([
p.symbol, p.status, p.amount, p.basePrice, p.pl
]))
})
const confirm = await readline.questionAsync(
'> Are you sure you want to claim the position(s) listed above? '
)
if (confirm.toLowerCase()[0] !== 'y') {
return
}
debug('')
debug('claiming positions...')
await Promise.all(positions.map(p => p.claim(rest)))
debug('done!')
})
'use strict'
const PI = require('p-iteration')
const _isEmpty = require('lodash/isEmpty')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-close-positions',
rest: { env: true, transform: true },
ws: { env: true, transform: true, connect: true, auth: true },
readline: true,
params: {
filterByMarket: null
}
}, async ({
debug, debugTable, rest, ws, params, readline
}) => {
const { filterByMarket } = params
const allPositions = await rest.positions()
if (allPositions.length === 0) {
debug('no open positions')
return
}
debug(
'found %d open positions on market(s) %s', allPositions.length,
allPositions.map(({ symbol }) => symbol).join(',')
)
const positions = _isEmpty(filterByMarket)
? allPositions
: allPositions.filter(({ symbol }) => symbol === filterByMarket)
if (positions.length === 0) {
debug('no positions match filter')
return
}
const orders = positions.map(p => p.orderToClose(ws))
debugTable({
headers: [
'ID', 'Symbol', 'Status', 'Amount', 'Base Price', 'P/L'
],
rows: positions.map(p => ([
p.id, p.symbol, p.status, p.amount, p.basePrice, p.pl
]))
})
orders.forEach(o => (debug('%s', o.toString())))
debug('')
const confirm = await readline.questionAsync(
'> Are you sure you want to submit the order(s) listed above? '
)
if (confirm.toLowerCase()[0] !== 'y') {
return
}
debug('')
ws.onOrderClose({}, ({ id, symbol, status }) => {
debug('received confirmation of order %d closed on %s: %s', id, symbol, status)
})
await PI.forEachSeries(orders, o => o.submit())
debug('')
debug('closed %d positions', positions.length)
})
'use strict'
const _chunk = require('lodash/chunk')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-currencies',
rest: true
}, async ({ debug, rest }) => {
debug('fetching currency list...')
const currencies = await rest.currencies()
debug('received %d currencies', currencies[0].length)
debug('')
_chunk(currencies[0], 10).forEach((currencyChunk) => {
debug('%s', currencyChunk.join(', '))
})
debug('')
})
'use strict'
const { prepareAmount } = require('bfx-api-node-util')
const argFromCLI = require('../util/arg_from_cli')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-funding-credits',
rest: { env: true },
params: {
symbol: argFromCLI(0, 'fUSD')
}
}, async ({ rest, debug, debugTable, params }) => {
const { symbol } = params
debug('fetching funding credits for %s', symbol)
const fcs = await rest.fundingCredits(symbol)
if (fcs.length === 0) {
debug('none available')
} else {
debugTable({
headers: ['Symbol', 'Amount', 'Status', 'Rate', 'Period', 'Renew'],
rows: fcs.map(fc => [
fc.symbol, prepareAmount(fc.amount), fc.status, fc.rate * 100,
fc.period, fc.renew ? 'Y' : 'N'
])
})
}
})
'use strict'
const { prepareAmount } = require('bfx-api-node-util')
const argFromCLI = require('../util/arg_from_cli')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-funding-info',
rest: { env: true },
params: {
symbol: argFromCLI(0, 'fUSD')
}
}, async ({ rest, debug, debugTable, params }) => {
const { symbol } = params
debug('fetching funding info for %s', symbol)
const flu = await rest.fundingInfo(symbol)
const [,, [yieldLoan, yieldLend, durationLoan, durationLend]] = flu
debugTable({
headers: [
'Symbol', 'Yield Loan', 'Yield Lend', 'Duration Loan', 'Duration Lend'
],
rows: [[
symbol, prepareAmount(yieldLoan), prepareAmount(yieldLend), durationLoan,
durationLend
]]
})
})
'use strict'
const { prepareAmount } = require('bfx-api-node-util')
const argFromCLI = require('../util/arg_from_cli')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-funding-loans',
rest: { env: true },
params: {
symbol: argFromCLI(0, 'fUSD')
}
}, async ({ rest, debug, debugTable, params }) => {
const { symbol } = params
debug('fetching funding loans for %s', symbol)
const fls = await rest.fundingLoans(symbol)
if (fls.length === 0) {
debug('none available')
} else {
debugTable({
headers: ['Symbol', 'Amount', 'Status', 'Rate', 'Period', 'Renew'],
rows: fls.map(fl => [
fl.symbol, prepareAmount(fl.amount), fl.status, fl.rate * 100,
fl.period, fl.renew ? 'Y' : 'N'
])
})
}
})
'use strict'
const { prepareAmount } = require('bfx-api-node-util')
const argFromCLI = require('../util/arg_from_cli')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-funding-offers',
rest: { env: true },
params: {
symbol: argFromCLI(0, 'fUSD')
}
}, async ({ rest, debug, debugTable, params }) => {
const { symbol } = params
debug('fetching funding offers for %s', symbol)
const fos = await rest.fundingOffers(symbol)
if (fos.length === 0) {
debug('none available')
} else {
debugTable({
headers: ['Symbol', 'Amount', 'Status', 'Rate', 'Period', 'Renew'],
rows: fos.map(fo => [
fo.symbol, prepareAmount(fo.amount), fo.status, fo.rate * 100,
fo.period, fo.renew ? 'Y' : 'N'
])
})
}
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-key-permissions',
rest: {
env: true,
transform: true
}
}, async ({ debug, debugTable, rest }) => {
debug('fetching permissions')
const perms = await rest.keyPermissions()
const rows = perms.map(({ key, read, write }) => [
key.toUpperCase(), read ? 'Y' : 'N', write ? 'Y' : 'N'
])
debugTable({
rows,
headers: ['Scope', 'Read', 'Write']
})
})
'use strict'
const { prepareAmount } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
const argFromCLI = require('../util/arg_from_cli')
module.exports = runExample({
name: 'rest-get-ledgers',
rest: { env: true, transform: true },
params: {
ccy: argFromCLI(0, 'all')
}
}, async ({ debug, debugTable, rest, params }) => {
const ccy = params.ccy === 'all' ? null : params.ccy
debug('fetching ledger entries for %s...', ccy || 'all currencies')
const entries = await rest.ledgers(ccy)
const rows = entries.map(e => [
e.id, e.currency, new Date(e.mts).toLocaleString(), prepareAmount(e.amount),
prepareAmount(e.balance), e.description
])
debugTable({
rows,
headers: [
'Entry ID', 'Currency', 'Timestamp', 'Amount', 'Balance', 'Description'
]
})
})
'use strict'
const _capitalize = require('lodash/capitalize')
const _isEmpty = require('lodash/isEmpty')
const { prepareAmount, preparePrice } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-list-open-orders',
rest: { env: true, transform: true },
params: {
filterByMarket: null
}
}, async ({ rest, debug, debugTable, params }) => {
const { filterByMarket } = params
debug('fetching open orders...')
const allOrders = await rest.activeOrders()
if (allOrders.length === 0) {
return debug('no open orders matching filters')
}
const orders = _isEmpty(filterByMarket)
? allOrders
: allOrders.filter(o => o.symbol === filterByMarket)
debug('read %d open order(s)', orders.length)
debugTable({
headers: [
'Symbol', 'Type', 'Amount', 'Price', 'Status', 'ID', 'CID',
'Created', 'Updated'
],
rows: orders.map((o) => [
o.symbol, o.type, prepareAmount(o.amount), preparePrice(o.price),
_capitalize(o.status.split(':')[0]), o.id, o.cid,
new Date(o.mtsCreate).toLocaleString(),
new Date(o.mtsUpdate).toLocaleString()
])
})
})
'use strict'
const { prepareAmount } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-margin-info',
rest: { env: true, transform: true }
}, async ({ debug, rest }) => {
debug('fetching margin info...')
const info = await rest.marginInfo()
const { userPL, userSwaps, marginBalance, marginNet } = info
debug('')
debug('Swaps: %d', userSwaps)
debug('P/L: %s', prepareAmount(userPL))
debug('Balance: %s', prepareAmount(marginBalance))
debug('Net Balance: %s', prepareAmount(marginNet))
debug('')
})
'use strict'
const { prepareAmount, preparePrice } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
const argFromCLI = require('../util/arg_from_cli')
module.exports = runExample({
name: 'rest-get-movements',
rest: { env: true, transform: true },
params: {
ccy: argFromCLI(0, 'all')
}
}, async ({ debug, debugTable, rest, params }) => {
const ccy = params.ccy === 'all' ? null : params.ccy
debug('fetching movements for %s...', ccy || 'all currencies')
const movements = await rest.movements(ccy)
if (movements.length === 0) {
return debug('no movements found')
}
debugTable({
headers: [
'ID', 'Currency', 'Started', 'Updated', 'Status', 'Amount', 'Fees'
],
rows: movements.map((m) => {
const status = `${m.status[0].toUpperCase()}${m.status.substring(1).toLowerCase()}`
const started = new Date(m.mtsStarted).toLocaleString()
const updated = new Date(m.mtsUpdated).toLocaleString()
return [
m.id, m.currency, started, updated, status, prepareAmount(m.amount),
preparePrice(m.fees)
]
})
})
})
'use strict'
const { prepareAmount, preparePrice } = require('bfx-api-node-util')
const _isEmpty = require('lodash/isEmpty')
const runExample = require('../util/run_example')
const START = Date.now() - (30 * 24 * 60 * 60 * 1000 * 1000)
const END = Date.now()
const LIMIT = 25
module.exports = runExample({
name: 'rest-get-order-history',
rest: { env: true, transform: true },
params: {
market: 'tBTCUSD'
}
}, async ({ debug, debugTable, rest, params }) => {
const { market } = params
if (_isEmpty(market)) {
return debug('market required')
}
debug('fetching 30d order history for %s...', market)
const orders = await rest.orderHistory(market, START, END, LIMIT)
if (orders.length === 0) {
return debug('no historical orders for %s', market)
}
debugTable({
headers: [
'Order ID', 'Created', 'Updated', 'Amount', 'Filled', 'Price', 'Status'
],
rows: orders.map(o => {
o.status = `${o.status[0].toUpperCase()}${o.status.substring(1)}`
o.mtsCreate = new Date(o.mtsCreate).toLocaleString()
o.mtsUpdate = new Date(o.mtsUpdate).toLocaleString()
return [
o.id, o.mtsCreate, o.mtsUpdate, prepareAmount(o.amountOrig),
prepareAmount(o.amountOrig - o.amount),
preparePrice(o.price), o.status.split(':')[0]
]
})
})
})
'use strict'
const _capitalize = require('lodash/capitalize')
const _map = require('lodash/map')
const { prepareAmount, preparePrice } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'positions',
rest: { env: true, transform: true }
}, async ({ debug, debugTable, rest }) => {
debug('fetching positions...')
const positions = await rest.positions()
const symbols = _map(positions, 'symbol')
if (positions.length === 0) {
return debug('no open positions')
}
debug('found %d open positions', positions.length)
debug('fetching tickers for: %s', symbols.join(', '))
const prices = {}
const rawTickers = await rest.tickers(symbols)
rawTickers.forEach(({ symbol, lastPrice }) => (prices[symbol] = +lastPrice))
debugTable({
headers: [
'ID', 'Symbol', 'Status', 'Amount', 'Base Price', 'Funding Cost',
'Base Value', 'Net Value', 'P/L', 'P/L %'
],
rows: positions.map((p) => {
const nv = +prices[p.symbol] * +p.amount
const pl = nv - (+p.basePrice * +p.amount)
const plPerc = (pl / nv) * 100.0
return [
p.id, p.symbol, _capitalize(p.status), prepareAmount(p.amount),
preparePrice(p.basePrice), prepareAmount(p.marginFunding),
prepareAmount(+p.marginFunding + (+p.amount * +p.basePrice)),
prepareAmount(nv), prepareAmount(pl), plPerc.toFixed(2)
]
})
})
})
'use strict'
const { PulseMessage } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-pulse',
rest: { env: true, transform: true }
}, async ({ debug, debugTable, rest }) => {
debug('gettting pulse history..')
const pulseHistRes = await rest.pulseHistory()
debug('pulse history response')
debugTable({
headers: [
'PID', 'MTS', 'PUID', 'TITLE', 'CONTENT', 'COMMENTS'
],
rows: pulseHistRes.map(({ id, mts, userID, title, content, comments }) => [
id,
new Date(mts).toLocaleString(),
userID,
(title && title.substring(0, 15)) || '-',
content.substring(0, 15),
comments // number of comments
])
})
const pulseMsg = new PulseMessage({
title: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
content: 'Contrary to popular belief, Lorem Ipsum is not simply random text.',
isPublic: 0,
isPin: 1
})
debug('submitting pulse message: %s', pulseMsg.toString())
let pulse
try {
pulse = await rest.addPulse(pulseMsg)
} catch (e) {
return debug('pulse message submittion failed: %s', e.message)
}
debug('pulse message submission response')
debugTable({
headers: [
'PID', 'MTS', 'PUID', 'TITLE', 'CONTENT'
],
rows: [[
pulse.id,
new Date(pulse.mts).toLocaleString(),
pulse.userID,
(pulse.title && pulse.title.substring(0, 15)) || '-',
pulse.content.substring(0, 15)
]]
})
const pulseComment = new PulseMessage({
parent: pulse.id,
content: 'No more seven warlords of the sea',
isPublic: 0,
isPin: 1
})
debug('submitting pulse comment: %s', pulseComment.toString())
let comment
try {
comment = await rest.addPulseComment(pulseComment)
} catch (e) {
return debug('pulse comment submittion failed: %s', e.message)
}
debug('pulse comment submission response')
debugTable({
headers: [
'PID', 'MTS', 'PARENT', 'PUID', 'COMMENT'
],
rows: [[
comment.id,
new Date(comment.mts).toLocaleString(),
comment.parent,
comment.userID,
comment.content.substring(0, 15)
]]
})
debug('gettting pulse comments..')
const pulseComments = await rest.fetchPulseComments({
parent: pulse.id,
isPublic: 0, // 0 for comments made by you; 1 for all comments of the pulse
limit: 3, // fetch given number of comments for this pulse
end: 0 // fetch comments from a given starttime in milliseconds
})
debug('pulse comments response')
debugTable({
headers: [
'PID', 'MTS', 'PUID', 'COMMENT'
],
rows: pulseComments.map(({ id, mts, userID, content }) => [
id,
new Date(mts).toLocaleString(),
userID,
content.substring(0, 15)
])
})
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-platform-status',
rest: true
}, async ({ debug, rest }) => {
debug('fetching platform status...')
const status = await rest.status()
debug(status === 0
? 'Platform currently under maintenance'
: 'Platform operating normally'
)
})
'use strict'
const Promise = require('bluebird')
const { FundingOffer } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
const CLOSE_DELAY_MS = 5 * 1000
module.exports = runExample({
name: 'rest-submit-funding-offer',
rest: { env: true, transform: true }
}, async ({ debug, rest, params }) => {
const fo = new FundingOffer({
type: 'LIMIT',
symbol: 'fUSD',
rate: 0.0120000,
amount: 120,
period: 2
}, rest)
debug('submitting: %s', fo.toString())
try {
await fo.submit()
} catch (e) {
return debug('failed: %s', e.message)
}
debug('done. closing in %ds...', CLOSE_DELAY_MS / 1000)
await Promise.delay(CLOSE_DELAY_MS)
await fo.close()
debug('offer closed')
})
'use strict'
const Promise = require('bluebird')
const { Order } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
const UPDATE_DELAY_MS = 5 * 1000
const CANCEL_DELAY_MS = 10 * 1000
module.exports = runExample({
name: 'rest-submit-order',
rest: { env: true },
readline: true,
params: {
// needed in order to pipe data to the process, until we can figure out a
// workaround
skipConfirm: false,
onlySubmitOrder: false, // allows this script to be used only for submits
symbol: 'tLEOUSD',
price: 2,
amount: -6,
type: Order.type.LIMIT,
affiliateCode: 'xZvWHMNR'
}
}, async ({ rest, debug, params, readline }) => {
const {
symbol, price, amount, type, affiliateCode, onlySubmitOrder, skipConfirm,
priceStop, distance
} = params
const o = new Order({
cid: Date.now(),
symbol,
price,
priceAuxLimit: priceStop,
priceTrailing: distance,
amount,
type,
affiliateCode
}, rest)
if (!skipConfirm) {
const confirm = await readline.questionAsync([
'> Are you sure you want to submit this order?',
`> ${o.toString()}`,
'> '
].join('\n'))
if (confirm.toLowerCase()[0] !== 'y') {
return
}
}
debug('submitting order: %s', o.toString())
await o.submit()
debug('order successfully submitted! (id %j, cid %j, gid %j)', o.id, o.cid, o.gid)
if (onlySubmitOrder) {
return // for bfx-cli
}
debug('')
debug('will update price to $3.00 in %fs...', UPDATE_DELAY_MS / 1000)
await Promise.delay(UPDATE_DELAY_MS)
debug('')
debug('updating order price...')
const updateNotification = await o.update({ price: 3 })
debug('successfully updated! (%s: %s)', updateNotification.status, updateNotification.text)
debug('')
debug('will cancel the order in %fs', CANCEL_DELAY_MS / 1000)
await Promise.delay(CANCEL_DELAY_MS)
debug('')
debug('cancelling order...')
const cancelNotification = await o.cancel()
debug('successfully canceled! (%s: %s)', cancelNotification.status, cancelNotification.text)
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-symbol-details',
rest: { transform: true }
}, async ({ rest, debug, debugTable }) => {
debug('fetching symbol details...')
const details = await rest.symbolDetails()
debugTable({
headers: [
'Pair', 'Initial Margin', 'Min Margin', 'Max Order',
'Min Order', 'Margin'
],
rows: details.map(({
pair, initialMargin, minimumMargin, // eslint-disable-line
maximumOrderSize, minimumOrderSize, margin // eslint-disable-line
}) => [
pair.toUpperCase(), initialMargin, minimumMargin, // eslint-disable-line
maximumOrderSize, minimumOrderSize, margin ? 'Y' : 'N' // eslint-disable-line
])
})
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-symbols',
rest: { transform: true }
}, async ({ rest, debug }) => {
debug('fetching symbol list...')
const symbols = await rest.symbols()
debug('read %d symbols', symbols.length)
debug('%s', symbols.map(s => `t${s.toUpperCase()}`).join(', '))
})
'use strict'
const { preparePrice, prepareAmount } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-get-tickers',
rest: { transform: true },
params: {
filterByMarket: null
}
}, async ({ rest, debug, debugTable, params }) => {
const { filterByMarket } = params
debug('fetching symbol list...')
const rawSymbols = await rest.symbols()
debug('read %d symbols', rawSymbols.length)
const symbols = rawSymbols
.map(s => `t${s.toUpperCase()}`)
.filter(s => (
!filterByMarket || (s === filterByMarket)
))
if (symbols.length === 0) {
return debug('no tickers match provided filters')
}
debug('fetching %d tickers...', symbols.length)
const tickers = await rest.tickers(symbols)
debugTable({
colWidths: [10, 14, 14, 14, 14, 14, 14, 18, 18],
headers: [
'Symbol', 'Last', 'High', 'Low', 'Daily Change', 'Bid', 'Ask', 'Bid Size',
'Ask Size'
],
rows: tickers.map(t => ([
t.symbol, preparePrice(t.lastPrice), preparePrice(t.high),
preparePrice(t.low), (t.dailyChange * 100).toFixed(2), preparePrice(t.bid),
preparePrice(t.ask), prepareAmount(t.bidSize), prepareAmount(t.askSize)
]))
})
})
'use strict'
const { prepareAmount, preparePrice } = require('bfx-api-node-util')
const _isEmpty = require('lodash/isEmpty')
const runExample = require('../util/run_example')
const START = Date.now() - (30 * 24 * 60 * 60 * 1000 * 1000)
const END = Date.now()
const LIMIT = 25
module.exports = runExample({
name: 'rest-get-trade-history',
rest: { env: true, transform: true },
params: {
symbol: 'tBTCUSD'
}
}, async ({ debug, debugTable, rest, params }) => {
const { symbol } = params
if (_isEmpty(symbol)) {
return debug('symbol required')
}
debug('fetching 30d trade history for %s...', symbol)
const trades = await rest.accountTrades(symbol, START, END, LIMIT)
if (trades.length === 0) {
return debug('no historical trades for %s', symbol)
}
debugTable({
headers: [
'Trade ID', 'Order ID', 'Created', 'Exec Amount', 'Exec Price', 'Fee'
],
rows: trades.map(t => [
t.id, t.orderID, new Date(t.mtsCreate).toLocaleString(),
prepareAmount(t.execAmount), preparePrice(t.execPrice),
`${t.fee} ${t.feeCurrency}`
])
})
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-transfer',
rest: { env: true, transform: true },
params: {
fromType: 'deposit',
fromCCY: 'USD',
toType: 'trading',
toCCY: 'USD',
amount: 1
}
}, async ({ debug, rest, params }) => {
const { fromType, fromCCY, toType, toCCY, amount } = params
debug(
'transferring %f from %s %s to %s %s',
amount, fromType, fromCCY, toType, toCCY
)
await rest.transfer({
amount: `${amount}`,
from: fromType,
currency: fromCCY,
to: toType,
currencyTo: toCCY
})
debug('done!')
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-wallet',
rest: { env: true, transform: true }
}, async ({ rest, debug }) => {
debug('Submitting new order...')
// get new deposit address
const address = await rest.getDepositAddress({
wallet: 'exchange',
method: 'bitcoin',
opRenew: 0
})
debug(`new wallet address ${address}`)
// transfer between accounts
const transferConfirmation = await rest.transfer({
from: 'exchange',
to: 'margin',
amount: 10,
currency: 'BTC',
currencyTo: 'BTC'
})
debug('transfer confirmed: %j', transferConfirmation)
// withdraw
const withdrawalConfirmation = await rest.withdraw({
wallet: 'exchange',
method: 'bitcoin',
amount: 2,
address: '1MUz4VMYui5qY1mxUiG8BQ1Luv6tqkvaiL'
})
debug('withdraw confirmed: %j', withdrawalConfirmation)
})
'use strict'
const _uniq = require('lodash/uniq')
const _capitalize = require('lodash/capitalize')
const _isFinite = require('lodash/isFinite')
const _isEmpty = require('lodash/isEmpty')
const { prepareAmount } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'rest-wallets',
rest: {
env: true,
transform: true
},
params: {
hideZeroBalances: true,
filterByType: false,
filterByCurrency: false,
valueCCY: 'USD'
}
}, async ({ debug, debugTable, rest, params }) => {
const {
valueCCY, hideZeroBalances, filterByType, filterByCurrency
} = params
const symbolForWallet = w => `t${w.currency}${valueCCY}`
debug('fetching balances')
const allWallets = await rest.wallets() // actual balance fetch
const balances = allWallets.filter(w => !( // filter as requested
(hideZeroBalances && +w.balance === 0) ||
(!_isEmpty(filterByType) && (w.type.toLowerCase() !== filterByType.toLowerCase())) ||
(!_isEmpty(filterByCurrency) && (w.currency.toLowerCase() !== filterByCurrency.toLowerCase()))
)).map(w => ({
...w,
currency: w.currency.toUpperCase(),
inValueCurrency: w.currency.toUpperCase() === valueCCY
}))
if (balances.length === 0) {
return debug('no wallets match provided filters')
}
debug('found %d balances', balances.length)
// Pull in ticker data for balances which are not in the requested value ccy
// Balance in BTC, value in USD -> We need to fetch tBTCUSD (last price)
const lastPrices = {}
const balancesToConvert = balances.filter(w => w.currency !== valueCCY)
const symbols = _uniq(balancesToConvert.map(symbolForWallet))
if (symbols.length > 0) {
debug('fetching tickers for: %s', symbols.join(', '))
const tickers = await rest.tickers(symbols)
tickers.forEach(({ symbol, lastPrice }) => (lastPrices[symbol] = +lastPrice))
}
let totalValue = 0
const rows = balances.map(({ currency, type, balance, balanceAvailable }) => {
const value = currency !== valueCCY
? (lastPrices[symbolForWallet({ currency })] * +balance) || 0
: +balance
totalValue += value
return [
_capitalize(type),
currency,
prepareAmount(balance),
prepareAmount(balanceAvailable),
...(_isFinite(value) ? [
prepareAmount(value),
currency !== valueCCY
? prepareAmount(lastPrices[symbolForWallet({ currency })])
: 1
] : [
'-',
'-'
])
]
})
debugTable({
rows,
headers: [
'Type', 'Symbol', 'Total', 'Available', `Value (${valueCCY})`,
`Unit Price (${valueCCY})`
]
})
debug('total value: %d %s', prepareAmount(totalValue), valueCCY)
})
'use strict'
const _isEmpty = require('lodash/isEmpty')
const _isFunction = require('lodash/isFunction')
/**
* Grabs an argument from the arguments list if we've been executed via node or
* npm
*
* @param {number} index - starting after invocation (2)
* @param {string} def - fallback value if none found/not supported
* @param {Function?} parser - optional, used to process value if provided
* @returns {string} value
*/
module.exports = (index, def, parser) => {
const val = /node/.test(process.argv[0]) || /npm/.test(process.argv[0])
? _isEmpty(process.argv[2 + index]) ? def : process.argv[2 + index]
: def
return _isFunction(parser)
? parser(val)
: val
}
'use strict'
const SocksProxyAgent = require('socks-proxy-agent')
const _isString = require('lodash/isString')
const _isEmpty = require('lodash/isEmpty')
const validArg = v => _isString(v) && !_isEmpty(v)
/**
* Grabs RESTv2/WSv2 constructor arguments from the environment, configuring
* the api credentials, connection agent, and connection URL
*
* @param {string?} urlKey - name of env var holding the connection URL
* @returns {object} envArgs
*/
module.exports = (urlKey) => {
const { API_KEY, API_SECRET, SOCKS_PROXY_URL } = process.env
const URL = process.env[urlKey]
const agent = validArg(SOCKS_PROXY_URL) && new SocksProxyAgent(SOCKS_PROXY_URL)
const envArgs = {}
if (agent) envArgs.agent = agent
if (validArg(URL)) envArgs.url = URL
if (validArg(API_KEY)) envArgs.apiKey = API_KEY
if (validArg(API_SECRET)) envArgs.apiSecret = API_SECRET
return envArgs
}
'use strict'
const Table = require('cli-table3')
/**
* Generates a CLI table and logs it to the console
*
* @param {object} args - arguments
* @param {object} args.rows - data
* @param {object} args.headers - column headers
* @param {object} args.debug - log function
* @returns {string} table
*/
module.exports = ({ rows, headers, debug }) => {
const t = new Table({
head: headers,
colWidths: [] // auto-compute
})
rows.forEach(r => t.push(r))
const str = t.toString()
str.split('\n').map(l => debug('%s', l))
return str
}
'use strict'
// Proxy to allow external stubbing
const debug = require('debug')
module.exports = { get: () => debug }
const dotenv = require('dotenv')
const Promise = require('bluebird')
const _isEmpty = require('lodash/isEmpty')
const _isobject = require('lodash/isObject')
const { RESTv2 } = require('bfx-api-node-rest')
const Readline = require('readline-promise').default
const WSv2 = require('../../lib/transports/ws2')
const argsFromEnv = require('./args_from_env')
const debugTableUtil = require('./debug_table')
const D = require('./debug').get()
/**
* Helper to execute an async example with a debugger pre-initialized and
* WSv2/RESTv2 instances if requested. Captures errors and logs helpful
* information in case of config load failure.
*
* @param {object} args - arguments
* @param {string} args.name - example name, used in debug string
* @param {boolean|object} [args.ws] - if passed, a WSv2 connection is provided
* @param {boolean} [args.ws.connect] - if true, the connection is opened
* @param {boolean} [args.ws.env] - if true, the instance receives credentials/connection info from .env
* @param {boolean} [args.ws.args] - further args, passed to WSv2 constructor
* @param {boolean|object} [args.rest] - if passed, a RESTv2 instance is provided
* @param {boolean} [args.rest.env] - if true, the instance receives credentials/connection info from .env
* @param {boolean} [args.rest.args] - further args, passed to RESTv2 constructor
* @param {object} [args.params] - optional parameters to pass to the example
* @param {Function} example - must return a promise
* @returns {Function} paramOverride - func that can be used to override params
*/
module.exports = (args = {}, example = () => { }) => {
const {
testing, name, ws, rest, readline, params = {}
} = args
if (_isEmpty(name)) {
throw new Error('no example name provided')
}
let timeout = null // saved so it can be cleared/exec prevented by the caller
let noCatch = false
/**
* TODO: extract timeout body
* we return an override method for params below the timeout block
*
* @param {object} paramOverrides - merged with default script parameters
* @returns {Promise} p - resolves on script completion
*/
const exec = (paramOverrides = {}) => {
Object.assign(params, paramOverrides)
return new Promise((resolve, reject) => {
timeout = setTimeout(async () => {
const debug = D('>')
debug.enabled = true
/**
* Log a table to the console
*
* @param {object} args - arguments
* @param {object[]} args.rows - data, can be specified as 2nd param
* @param {string[]} args.headers - column labels
* @param {number[]} args.widths - column widths
* @param {object[]} extraRows - optional row spec as 2nd param
*/
const debugTable = ({ rows = [], headers, widths }, extraRows = []) => {
debug('')
debugTableUtil({
rows: [...rows, ...extraRows],
headers,
widths,
debug
})
debug('')
}
const { env: restEnv, ...restArgs } = _isobject(rest) ? rest : {}
const {
env: wsEnv,
connect: wsAutoConnect,
keepOpen: wsKeepOpen,
auth: wsAutoAuth,
...wsArgs
} = _isobject(ws) ? ws : {}
// load .env if needed for either API transport
if (restEnv || wsEnv) {
dotenv.config()
}
// Build up example toolset, including API transports
let toolset
try {
toolset = {
env: argsFromEnv(),
params,
debug,
debugTable,
...(!readline ? {} : {
readline: Readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
})
}),
...(!rest ? {} : {
rest: new RESTv2({
...(restEnv ? argsFromEnv('REST_URL') : {}),
...restArgs
})
}),
...(!ws ? {} : {
ws: new WSv2({
...(wsEnv ? argsFromEnv('WS_URL') : {}),
...wsArgs
})
})
}
} catch (e) {
debug('error loading config: %s', e.message)
debug('did you create a .env file in the current directory?')
debug('')
debug('supported values in .env are:')
debug('')
debug('API_KEY=...')
debug('API_SECRET=...')
debug('WS_URL=...')
debug('REST_URL=...')
debug('SOCKS_PROXY_URL=...')
debug('VERBOSE_ERRORS=...')
return resolve()
}
if (toolset.ws) {
toolset.ws.on('error', e => debug('WSv2 error: %s', e.message | e))
}
if (wsAutoConnect) {
if (!testing) debug('connecting with WSv2...')
await toolset.ws.open()
}
if (wsAutoAuth) {
if (!testing) debug('authenticating with WSv2...')
await toolset.ws.auth()
}
const startMTS = Date.now()
try {
await example(toolset)
} catch (e) {
if (testing || noCatch) {
reject(e) // propagate, needed for tests
} else {
debug('error: %s', (process.env.VERBOSE_ERRORS ? e.stack : e.message) || e)
}
} finally {
if (toolset.ws && toolset.ws.isOpen() && !wsKeepOpen) {
await toolset.ws.close()
}
if (toolset.readline) {
toolset.readline.close()
}
}
if (!testing) {
debug('finished in %fs', (Date.now() - startMTS) / 1000)
}
resolve()
}, 0)
})
}
// auto exec by default, can be stopped via skipAutoExec below due to timeout
exec().catch((e) => {
D('>')('error: %s', (process.env.VERBOSE_ERRORS ? e.stack : e.message) || e)
})
return {
skipAutoExec: () => clearTimeout(timeout),
exec: async (params) => {
noCatch = true // called externally, don't auto-catch errors
return exec(params)
}
}
}
'use strict'
process.env.DEBUG = 'bfx:examples:*'
const _flatten = require('lodash/flatten')
const debug = require('debug')('bfx:examples:ws2-manager')
const runExample = require('./util/run_example')
const Manager = require('../lib/ws2_manager')
module.exports = runExample({
name: 'ws2-manger',
rest: { transform: true }
}, async ({ rest, env }) => {
debug('fetching symbol details...')
const details = await rest.symbolDetails()
const symbols = details.map(d => `t${d.pair.toUpperCase()}`)
const timeFrames = ['1m', '5m', '30m', '1h', '6h']
const keys = _flatten(symbols.map(s => {
return timeFrames.map(tf => `trade:${tf}:${s}`)
}))
const m = new Manager({ ...env })
m.on('error', (err) => {
debug('error: %s', err)
})
m.once('open', () => {
debug('open')
keys.forEach(key => {
m.subscribeCandles(key)
m.onCandle({ key }, (candles) => {
debug('recv %d candles on channel %s', candles.length, key)
})
})
symbols.forEach(symbol => {
m.subscribeTrades(symbol)
m.onTrades({ symbol }, (trades) => {
debug('recv %d trades on channel %s', trades.length, symbol)
})
})
symbols.forEach(symbol => {
m.subscribeTicker(symbol)
m.onTicker({ symbol }, (ticker) => {
debug('recv ticker on channel %s: %j', symbol, ticker)
})
})
symbols.forEach(symbol => {
m.subscribeOrderBook(symbol)
m.onOrderBook({ symbol }, (update) => {
debug('recv book update on channel %s: %j', symbol, update)
})
})
setInterval(() => {
debug('num keys: %d', keys.length)
debug('num sockets: %d', m.getNumSockets())
debug('socket info: %j', m.getSocketInfo())
}, 5000)
})
m.openSocket()
})
'use strict'
const { Order } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
const SYMBOL = 'tBTCUSD'
module.exports = runExample({
name: 'ws2-atomic-order-update',
ws: {
env: true,
transform: true,
manageOrderBooks: true,
packetWDDelay: 10 * 1000,
autoReconnect: true,
seqAudit: true,
connect: true,
auth: true
}
}, async ({ ws, debug }) => {
const orderSent = false
await ws.subscribeOrderBook(SYMBOL, 'P0', '25')
debug('subscribed to order book %s:P0:25', SYMBOL)
ws.onOrderBook({ symbol: SYMBOL }, async (ob) => {
const topBidL = ob.topBidLevel()
if (topBidL === null || orderSent) {
return
}
debug('taking out price level: %j', topBidL)
const o = new Order({
symbol: SYMBOL,
type: Order.type.EXCHANGE_LIMIT,
price: topBidL[0],
amount: topBidL[2] * -1.1 // sell through top bid
}, ws)
debug('submitting: %s', o.toString())
o.registerListeners()
await o.submit()
debug('order submitted')
o.once('update', async (o) => {
debug('got order update: %s', o.status)
if (!o.isPartiallyFilled()) {
return
}
debug('order is partially filled, amount %f', o.amount)
debug('increasing amount w/ delta %f', o.amount * 2)
await o.update({ delta: `${o.amount * 2}` })
debug('order updated, new amount %f', o.amount)
debug('setting price to %f', o.price * 1.05)
await o.update({ price: `${o.price * 1.05}` })
debug('order updated, new price %f', o.price)
})
})
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-auth',
ws: { env: true } // manually open/auth to show usage
}, async ({ ws, debug }) => {
// register a callback for any order snapshot that comes in (account orders)
ws.onOrderSnapshot({}, (orders) => {
debug(`order snapshot: ${JSON.stringify(orders, null, 2)}`)
})
await ws.open()
debug('open')
await ws.auth()
debug('authenticated')
// do something with authenticated ws stream
})
'use strict'
const Promise = require('bluebird')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-calc',
ws: { env: true, connect: true, auth: true }
}, async ({ ws, debug }) => {
await Promise.delay(5 * 1000)
ws.requestCalc([
'margin_sym_tBTCUSD',
'position_tBTCUSD',
'wallet_margin_BTC',
'wallet_funding_USD'
])
// Watch log output for balance update packets (wu, miu, etc)
debug('sent calc, closing in 30s...')
await Promise.delay(30 * 1000)
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-cancel-all-buffered',
ws: {
env: true,
connect: true,
auth: true,
transform: true,
orderOpBufferDelay: 250
}
}, async ({ ws, debug }) => {
ws.onOrderSnapshot({}, async (snapshot) => {
if (snapshot.length === 0) {
debug('no orders to cancel')
return
}
debug('canceling %d orders', snapshot.length)
await ws.cancelOrders(snapshot)
debug('cancelled all orders')
})
})
'use strict'
const Promise = require('bluebird')
const _isEmpty = require('lodash/isEmpty')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'cancel-all-orders',
ws: { env: true, transform: true }, // no auto-auth so we can grab snapshot
readline: true,
params: {
filterByMarket: null
}
}, async ({ ws, debug, readline, params }) => {
await ws.open()
const { filterByMarket } = params
debug('awaiting order snapshot...')
const allOrders = await new Promise((resolve) => {
ws.onOrderSnapshot({}, resolve)
return ws.auth()
})
if (allOrders.length === 0) {
return debug('no orders to cancel')
}
const orders = _isEmpty(filterByMarket)
? allOrders
: allOrders.filter(o => o.symbol === filterByMarket)
debug('received snapshot (%d orders)', orders.length)
debug('')
orders.forEach(o => debug('%s', o.toString()))
debug('')
const confirm = await readline.questionAsync(
'> Are you sure you want to close the orders(s) listed above? '
)
if (confirm.toLowerCase()[0] !== 'y') {
return
}
debug('')
debug('cancelling all..')
const confirmations = await ws.cancelOrders(orders)
debug(
'done! cancelled the following order IDs: %s',
confirmations.map(o => o[0]).join(', ')
)
})
'use strict'
const _isEmpty = require('lodash/isEmpty')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-candles',
ws: {
env: true,
connect: true,
manageCandles: true, // enable candle dataset persistence/management
transform: true, // converts ws data arrays to Candle models (and others)
keepOpen: true
},
params: {
market: 'tBTCUSD',
tf: '5m'
}
}, async ({ ws, debug, params }) => {
const { market, tf } = params
if (_isEmpty(market)) throw new Error('market required')
if (_isEmpty(tf)) throw new Error('time frame required')
const candleKey = `trade:${tf}:${market}`
let prevTS = null
ws.onCandle({ key: candleKey }, (candles) => {
if (candles[0].mts === prevTS) {
return
}
const c = candles[1] // report previous candle
debug('%s %s open: %f, high: %f, low: %f, close: %f, volume: %f',
candleKey, new Date(c.mts).toLocaleTimeString(),
c.open, c.high, c.low, c.close, c.volume
)
prevTS = candles[0].mts
})
await ws.subscribeCandles(candleKey)
})
'use strict'
const runExample = require('../util/run_example')
const symbol = 'fUSD'
module.exports = runExample({
name: 'ws2-funding-info',
ws: { env: true, connect: true, auth: true, transform: true }
}, async ({ ws, debug }) => {
ws.onFundingInfoUpdate({}, fiu => {
fiu.forEach(fl => debug('fl: %j', fl.toJS()))
})
ws.requestCalc([`funding_sym_${symbol}`])
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-info-events',
ws: {
env: true, connect: true, auth: true, autoReconnect: true
}
}, async ({ ws, debug }) => {
ws.onMaintenanceStart(() => {
debug('info: maintenance period started')
// pause activity untill further notice
})
ws.onMaintenanceEnd(() => {
debug('info: maintenance period ended')
// resume activity
})
ws.onServerRestart(() => {
debug('info: bitfinex ws server restarted')
// await ws.reconnect() // if not using autoReconnect
})
})
'use strict'
const { Liquidations } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-liquidations',
ws: { env: true, connect: true, keepOpen: true }
}, async ({ ws, debug }) => {
ws.onStatus({ key: 'liq:global' }, (data) => {
data.forEach(liq => (
debug('liquidation: %s', new Liquidations(liq).toString())
))
})
await ws.subscribeStatus('liq:global')
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-notifications',
ws: { env: true, connect: true, auth: true, transform: true }
}, async ({ ws, debug }) => {
ws.onNotification({ type: '*' }, (n) => {
debug('recv notification: %j', n.toJS())
})
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-notify-ui',
ws: { env: true, connect: true, auth: true, transform: true }
}, async ({ ws, debug }) => {
ws.notifyUI({
type: 'success',
message: 'This is a test notification sent via the WSv2 API'
})
debug('notification sent')
})
'use strict'
const WSv2 = require('../../lib/transports/ws2')
const runExample = require('../util/run_example')
const SYMBOL = 'tXRPBTC'
const PRECISION = 'P0'
const LENGTH = '25'
module.exports = runExample({
name: 'ws2-ob-checksum',
ws: {
env: true,
connect: true,
transform: true,
manageOrderbooks: true // managed OBs are verified against incoming checksums
}
}, async ({ ws, debug }) => {
ws.onOrderBookChecksum({
symbol: SYMBOL,
prec: PRECISION,
len: LENGTH
}, cs => {
debug('recv valid cs for %s:%s:%s %d', SYMBOL, PRECISION, LENGTH, cs)
})
await ws.enableFlag(WSv2.flags.CHECKSUM)
await ws.subscribeOrderBook(SYMBOL, PRECISION, LENGTH)
})
'use strict'
const { Order } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
const oA = new Order({
symbol: 'tBTCUSD',
price: 200,
amount: 1,
type: 'EXCHANGE LIMIT'
})
const oB = new Order({
symbol: 'tETHUSD',
price: 50,
amount: 1,
type: 'EXCHANGE LIMIT'
})
module.exports = runExample({
name: 'ws2-oc-multi',
ws: {
env: true,
connect: true,
auth: true,
transform: true,
keepOpen: true
}
}, async ({ ws, debug }) => {
oA.registerListeners(ws)
oB.registerListeners(ws)
await oA.submit()
debug('created order A')
await oB.submit()
debug('created order B')
let oAClosed = false
let oBClosed = false
oA.on('close', async () => {
debug('order A cancelled: %s', oA.status)
oAClosed = true
if (oBClosed) return ws.close()
})
oB.on('close', async () => {
debug('order B cancelled: %s', oB.status)
oBClosed = true
if (oAClosed) return ws.close()
})
ws.send([0, 'oc_multi', null, {
id: [oA.id, oB.id]
}])
debug('sent oc_multi for orders A & B')
})
'use strict'
const Promise = require('bluebird')
const { Order } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
// Build new order
const o = new Order({
cid: Date.now(),
symbol: 'tBTCUSD',
type: Order.type.EXCHANGE_LIMIT,
amount: -0.05,
oco: true,
price: 2000,
priceAuxLimit: 1000
})
module.exports = runExample({
name: 'ws2-oco-order',
ws: { env: true, connect: true, auth: true, transform: true }
}, async ({ ws, debug }) => {
o.registerListeners(ws) // enable automatic updates
let orderClosed = false
o.on('update', () => {
debug('updated: %s', o.toString())
})
o.on('close', () => {
debug('order closed: %s', o.status)
orderClosed = true
})
debug('submitting order %d', o.cid)
await o.submit()
debug('got submit confirmation for order %d [%d]', o.cid, o.id)
// wait a bit...
await Promise.delay(2 * 1000)
if (orderClosed) {
return debug('order closed prematurely; did it auto-fill?')
}
debug('canceling...')
await o.cancel()
debug('got cancel confirmation for order %d', o.cid)
})
'use strict'
const blessed = require('blessed')
const blessedContrib = require('blessed-contrib')
const _isEmpty = require('lodash/isEmpty')
const _reverse = require('lodash/reverse')
const { preparePrice, prepareAmount } = require('bfx-api-node-util')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-order-book-viz',
ws: {
env: true,
connect: true,
transform: true,
manageOrderBooks: true, // tell the ws client to maintain full sorted OBs
keepOpen: true
},
params: {
market: 'tBTCUSD'
}
}, async ({ ws, params }) => {
const { market } = params
if (_isEmpty(market)) {
throw new Error('market required')
}
const screen = blessed.screen()
const bookTable = blessedContrib.table({
fg: 'white',
label: `Order Book ${market}`,
border: {
type: 'line',
fg: 'green'
},
columnSpacing: 5,
columnWidth: [10, 20, 10]
})
screen.append(bookTable)
ws.onOrderBook({ symbol: market }, (ob) => {
const data = []
_reverse(ob.asks).forEach(row => data.push([
preparePrice(row[0]), prepareAmount(row[2]), row[1]
]))
ob.bids.forEach(row => data.push([
preparePrice(row[0]), prepareAmount(row[2]), row[1]
]))
bookTable.setData({
headers: ['Price', 'Amount', 'Count'],
data
})
screen.render()
})
await ws.subscribeOrderBook(market, 'P0', '25')
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-order-books',
ws: {
env: true,
connect: true,
transform: true, // auto-transform array OBs to OrderBook objects
manageOrderBooks: true // tell the ws client to maintain full sorted OBs
}
}, async ({ ws, debug }) => {
let lastMidPrice = -1
let midPrice
// 'ob' is a full OrderBook instance, with sorted arrays 'bids' & 'asks'
ws.onOrderBook({ symbol: 'tBTCUSD' }, (ob) => {
midPrice = ob.midPrice()
if (midPrice !== lastMidPrice) {
debug(
'BTCUSD mid price: %d (bid: %d, ask: %d)',
midPrice, ob.bids[0][0], ob.asks[0][0]
)
}
lastMidPrice = midPrice
})
await ws.subscribeOrderBook('tBTCUSD')
})
'use strict'
const { Order } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
const o = new Order({
cid: Date.now(),
symbol: 'tBTCUSD',
price: 17833.5,
amount: -0.02,
type: Order.type.LIMIT,
tif: '2019-03-08 15:00:00'
})
module.exports = runExample({
name: 'ws2-order-tif',
ws: { env: true, connect: true, auth: true, transform: true }
}, async ({ ws, debug }) => {
o.registerListeners(ws)
o.on('update', () => debug('updated: %s', o.toString()))
o.on('close', () => debug('order closed: %s', o.status))
debug('submitting order %d', o.cid)
await o.submit()
debug(
'got submit confirmation for order %d [%d] [tif: %d]',
o.cid, o.id, o.mtsTIF
)
})
'use strict'
const Promise = require('bluebird')
const { Order } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
// Build new order
const o = new Order({
cid: Date.now(),
symbol: 'tBTCUSD',
price: 589.10,
amount: -0.02,
type: Order.type.EXCHANGE_LIMIT
})
module.exports = runExample({
name: 'ws2-orders',
ws: { env: true, connect: true, auth: true, transform: true }
}, async ({ ws, debug }) => {
let orderClosed = false
// Enable automatic updates
o.registerListeners(ws)
o.on('update', () => debug('updated: %s', o.toString()))
o.on('close', () => {
debug('order closed: %s', o.status)
orderClosed = true
})
debug('submitting order %d', o.cid)
await o.submit()
debug('got submit confirmation for order %d [%d]', o.cid, o.id)
// wait a bit...
await Promise.delay(2 * 1000)
if (orderClosed) {
return debug('order closed prematurely; did it auto-fill?')
}
debug('canceling...')
await o.cancel()
debug('got cancel confirmation for order %d', o.cid)
})
'use strict'
const { Order } = require('bfx-api-node-models')
const runExample = require('../util/run_example')
const oA = new Order({
symbol: 'tBTCUSD',
price: 200,
amount: 1,
type: 'EXCHANGE LIMIT'
})
const oB = new Order({
symbol: 'tETHUSD',
price: 50,
amount: 1,
type: 'EXCHANGE LIMIT'
})
const oC = new Order({
symbol: 'tETHBTC',
price: 1,
amount: 1,
type: 'EXCHANGE LIMIT'
})
module.exports = runExample({
name: 'ws2-ox-multi',
ws: {
env: true,
connect: true,
auth: true,
transform: true,
keepOpen: true
}
}, async ({ ws, debug }) => {
oA.registerListeners(ws)
oB.registerListeners(ws)
oC.registerListeners(ws)
let oAClosed = false
let oBClosed = false
let oCClosed = false
oA.on('close', async () => {
debug('order A cancelled: %s', oA.status)
oAClosed = true
if (oBClosed && oCClosed) return ws.close()
})
oB.on('close', async () => {
debug('order B cancelled: %s', oB.status)
oBClosed = true
if (oAClosed && oCClosed) return ws.close()
})
oC.on('close', async () => {
debug('order C cancelled: %s', oC.status)
oCClosed = true
if (oAClosed && oBClosed) return ws.close()
})
await oA.submit()
debug('created order A')
await oB.submit()
debug('created order B')
await oC.submit()
debug('created order C')
ws.submitOrderMultiOp([
['oc', { id: oA.id }],
['oc_multi', { id: [oB.id, oC.id] }]
])
debug('sent ox_multi to cancel order A and orders [B, C]')
})
'use strict'
const _isArray = require('lodash/isArray')
const _isFinite = require('lodash/isFinite')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-sequencing',
ws: { env: true, connect: true, auth: true }
}, async ({ ws, debug }) => {
// Enables internal sequence tracking; an error will be emitted if there is a
// seq # mis-match
await ws.enableSequencing({ audit: true })
if (!ws.isFlagEnabled(65536)) {
throw new Error('seq enable succeeded, but flag not updated')
}
debug('sequencing enabled')
await ws.subscribeTrades('tBTCUSD')
ws.on('message', (msg) => {
if (!_isArray(msg)) return // only array messages have sequence #s
// auth seq number, available as the last element on chan 0 packets
const authSeq = msg[0] === 0 && msg[1] !== 'hb'
? msg[msg.length - 1]
: NaN
// public seq number, last or 2nd to last element on all packets
const seq = msg[0] === 0 && msg[1] !== 'hb'
? msg[msg.length - 2]
: msg[msg.length - 1]
if (!_isFinite(authSeq)) {
debug('recv public seq # %d', seq)
} else {
debug('recv public seq # %d, auth seq # %d', seq, authSeq)
}
})
})
'use strict'
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-tickers',
ws: { env: true, connect: true, transform: true }
}, async ({ ws, debug }) => {
ws.onTicker({ symbol: 'tETHUSD' }, (ticker) => {
debug('ETH/USD ticker: %j', ticker.toJS())
})
ws.onTicker({ symbol: 'fUSD' }, (ticker) => {
debug('fUSD ticker: %j', ticker.toJS())
})
await ws.subscribeTicker('tETHUSD')
await ws.subscribeTicker('fUSD')
})
'use strict'
const _isEmpty = require('lodash/isEmpty')
const runExample = require('../util/run_example')
module.exports = runExample({
name: 'ws2-trades',
ws: {
env: true,
connect: true,
keepOpen: true,
transform: true
},
params: {
market: 'tBTCUSD'
}
}, async ({ ws, debug, params }) => {
const { market } = params
if (_isEmpty(market)) {
throw new Error('market required')
}
if (market[0] === 't') {
ws.onTradeEntry({ symbol: market }, (trade) => {
debug('trade on %s: %s', market, trade.toString())
})
} else {
ws.onFundingTradeEntry({ symbol: market }, (trade) => {
debug('funding trade: %s', trade.toString())
})
}
ws.onAccountTradeEntry({ symbol: market }, (trade) => {
debug('account trade: %s', trade.toString())
})
await ws.subscribeTrades(market)
})
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const _isUndefined = require('lodash/isUndefined')
const _isObject = require('lodash/isObject')
const _isString = require('lodash/isString')
const _isEmpty = require('lodash/isEmpty')
const SocksProxyAgent = require('socks-proxy-agent')
const argsFromEnv = require('../../examples/util/args_from_env')
describe('argsFromEnv', () => {
it('pulls api credentials from the environment only if available', () => {
delete process.env.API_KEY
delete process.env.API_SECRET
let args = argsFromEnv()
assert.ok(_isObject(args), 'did not return an object')
assert(_isUndefined(args.apiKey), 'api key parsed although not present on env')
assert(_isUndefined(args.apiSecret), 'api secret parsed although not present on env')
process.env.API_KEY = '42'
process.env.API_SECRET = '9000'
args = argsFromEnv()
assert.ok(_isObject(args), 'did not return an object')
assert.strictEqual(args.apiKey, '42', 'api key not pulled from env')
assert.strictEqual(args.apiSecret, '9000', 'api secret not pull from env')
})
it('provides a connection agent if a socks proxy url is available on the env', () => {
const url = 'socks4://localhost:9998'
delete process.env.SOCKS_PROXY_URL
let args = argsFromEnv()
assert.ok(_isObject(args), 'did not return an object')
assert.ok(_isUndefined(args.agent), 'agent provided although no config on env')
process.env.SOCKS_PROXY_URL = url
args = argsFromEnv()
assert.ok(_isObject(args), 'did not return an object')
assert.ok(args.agent instanceof SocksProxyAgent, 'did not provide a SocksProxyAgent instance')
assert.ok(/localhost/.test(args.agent.proxy.host), 'provided agent does not use proxy url from env')
})
it('provides a connection url only if available', () => {
const url = 'localhost:8080'
delete process.env.TEST_URL
let args = argsFromEnv('TEST_URL')
assert.ok(_isObject(args), 'did not return an object')
assert.ok(_isUndefined(args.url), 'url provided although no config on env')
process.env.TEST_URL = url
args = argsFromEnv('TEST_URL')
assert.ok(_isObject(args), 'did not return an object')
assert.ok(_isString(args.url) && !_isEmpty(args.url), 'connection url not pulled from env')
assert.strictEqual(args.url, url, 'provided url does not match env var')
})
})
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const debugTable = require('../../examples/util/debug_table')
describe('debugTable', () => {
it('throws an error if row, header, and column counts don\'t match', () => {
try {
debugTable({
rows: [[1]],
headers: ['', ''],
widths: [20, 20, 20],
debug: () => {}
})
assert.fail('no error was thrown')
} catch (e) {
assert.ok(true)
}
})
it('prints the table out line by line, and returns it as a multi-line string', () => {
let debugLineCount = 0
const str = debugTable({
rows: [[1, 1, 1], [2, 2, 2], [3, 3, 3]],
headers: ['', '', ''],
widths: [20, 20, 20],
debug: () => debugLineCount++
})
assert.strictEqual(str.split('\n').length, debugLineCount)
})
})
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const Promise = require('bluebird')
const _isObject = require('lodash/isObject')
const _isFunction = require('lodash/isFunction')
const { RESTv2 } = require('bfx-api-node-rest')
const runExample = require('../../examples/util/run_example')
const WSv2 = require('../../lib/transports/ws2')
const getRunArgs = (override = {}) => ({
name: 'self-test',
testing: true,
...override
})
describe('runExample', () => {
it('throws an error if no example name is provided', () => {
try {
runExample({}, () => (assert.fail('example should not have executed')))
assert.fail('error should have been thrown with missing name')
} catch (e) {
assert.ok(!!e)
}
try {
runExample({ name: '' }, async () => (assert.fail('example should not have executed')))
assert.fail('error should have been thrown with empty name')
} catch (e) {
assert.ok(!!e)
}
})
it('supports async examples', (done) => {
try {
runExample(getRunArgs(), async () => {
await Promise.delay(10)
done()
})
} catch (e) {
assert.fail('should not have thrown an error with an async example func')
}
})
it('passes a debugger to the example', (done) => {
runExample(getRunArgs(), async (args) => {
assert.ok(_isObject(args), 'example not passed a tooling object')
assert.ok(_isFunction(args.debug), 'example not passed a debug() instance')
done()
})
})
it('passes a specialized table logging function to the example', (done) => {
runExample(getRunArgs(), async (args) => {
assert.ok(_isObject(args), 'example not passed a tooling object')
assert.ok(_isFunction(args.debugTable), 'example not passed a debugTable() instance')
done()
})
})
it.skip('catches example errors', async () => {
let exampleExecuted = false
try {
await runExample(getRunArgs({ testing: false }), () => {
exampleExecuted = true
throw new Error('test error')
})()
} catch (e) {
assert.fail('example error should have been caught internally')
}
assert.ok(exampleExecuted, 'example was not executed')
})
it.skip('does not catch errors if testing is provided', () => {
let errorThrown = false
try {
runExample(getRunArgs(), async () => {
throw new Error('test error')
})
assert.fail('error was not propagated')
} catch (e) {
errorThrown = true
}
assert.ok(errorThrown, 'error was not thrown')
})
it('provides a RESTv2 client if requested', (done) => {
runExample(getRunArgs({ rest: true }), async ({ rest }) => {
assert.ok(rest instanceof RESTv2, 'no RESTv2 instance provided to example')
done()
})
})
it('parses .env and passes data to RESTv2 constructor if requested', async () => {
process.env.SOCKS_PROXY_URL = 'socks4://127.0.0.1:9998'
process.env.REST_URL = 'localhost:8080'
await runExample(getRunArgs({
rest: { env: true }
}), ({ rest }) => {
assert.ok(rest.usesAgent(), 'RESTv2 instance provided to example does not use .env config')
delete process.env.SOCKS_PROXY_URL
delete process.env.REST_URL
})
return runExample(getRunArgs({
rest: { env: true }
}), async ({ rest }) => {
assert.ok(!rest.usesAgent(), 'RESTv2 instance provided to example uses .env config when not requested')
assert.strictEqual(rest.getURL(), RESTv2.url, 'RESTv2 instance provided to example does not use default URL when no override configured')
})
})
it('passes extra RESTv2 args to constructor if provided', (done) => {
const URL = 'http://google.com'
runExample(getRunArgs({
rest: { url: URL }
}), ({ rest }) => {
assert.ok(rest instanceof RESTv2, 'did not receive a RESTv2 instance')
assert.strictEqual(rest.getURL(), URL, 'RESTv2 args were not passed through')
done()
})
}).timeout(10000)
it('provides a WSv2 client if requested', (done) => {
runExample(getRunArgs({
ws: true
}), ({ ws }) => {
assert.ok(ws instanceof WSv2, 'no WSv2 instance provided to example')
done()
})
}).timeout(10000)
it('parses .env and passes data to WSv2 constructor if requested', async () => {
process.env.SOCKS_PROXY_URL = 'socks4://127.0.0.1:9998'
process.env.WS_URL = 'localhost:8080'
await runExample(getRunArgs({
ws: { env: true }
}), ({ ws }) => {
assert.ok(ws.usesAgent(), 'WSv2 instance provided to example not given connection agent')
assert.strictEqual(ws.getURL(), 'localhost:8080', 'WSv2 instance provided to example not given connection url')
delete process.env.SOCKS_PROXY_URL
delete process.env.WS_URL
})
return runExample(getRunArgs({
ws: { env: true }
}), ({ ws }) => {
assert.ok(!ws.usesAgent(), 'WSv2 instance provided to example used connection agent when none configured')
assert.strictEqual(ws.getURL(), WSv2.url, 'WSv2 instance provided to example does not use default URL when no override configured')
})
}).timeout(10000)
it('passes extra WSv2 args to constructor if provided', (done) => {
runExample(getRunArgs({
ws: { seqAudit: true }
}), async ({ ws }) => {
assert.ok(ws instanceof WSv2, 'did not receive a WSv2 instance')
assert.ok(ws.sequencingEnabled(), 'WSv2 args were not passed through')
done()
})
}).timeout(10000)
it('closes WSv2 on example end if left open', (done) => {
runExample(getRunArgs({
ws: true
}), async ({ ws }) => {
ws.on('close', done)
await ws.open()
})
}).timeout(6000)
it('does not close WSv2 on example end if requested not too', (done) => {
runExample(getRunArgs({
ws: { connect: true, keepOpen: true }
}), async ({ ws }) => {
setTimeout(async () => {
ws.once('close', done)
await ws.close()
}, 20)
})
}).timeout(4000)
it('provides a readline instance if requested', () => {
runExample(getRunArgs({ readline: true }), ({ readline }) => {
assert.ok(_isFunction(readline.questionAsync), 'no readline instance provided')
})
})
it('automatically closes the readline instance if provided and not already closed', (done) => {
runExample(getRunArgs({ readline: true }), ({ readline }) => {
readline.on('close', done)
})
})
}).timeout(10 * 1000) // timeout for travis
[0.0009239,0.00071,30,5000,0.0009239,2,44568.06495174,0.00044901,0.3207,0.001849,14032554.7966796,0,0]
[1781.8,3.10227283,1781.9,1.44527318,-35.7,-0.0196,1781.8,13402.66689773,1834.2,1726.3]
[[6735741,1494853437352,-7342.32130201,0.00179797,30],[6735740,1494853437232,-6.22726787,0.00179797,30],[6735734,1494853434026,-4078.57360794,0.00179797,30],[6735730,1494853432487,-1438.49972447,0.00179797,30],[6735729,1494853431991,-195.61999655,0.00179797,30],[6735728,1494853431651,-2219.23318615,0.00179797,30],[6735727,1494853431627,-74.47875318,0.00179797,5],[6735726,1494853431609,-1234.13448145,0.00179797,30],[6735725,1494853431595,-66.99,0.00179797,30],[6735724,1494853431581,-56.22,0.00179797,30],[6735723,1494853431564,-233.66,0.00179797,30],[6735722,1494853431549,-95.94,0.00179797,30],[6735721,1494853431532,-591.74236565,0.00179794,30],[6735720,1494853431514,-513.37126375,0.00179794,30],[6735719,1494853431501,-59.08787049,0.00179792,2],[6735718,1494853431486,-59.08787049,0.00179791,30],[6735717,1494853431473,-50.49751111,0.00179787,30],[6735716,1494853431461,-50.49751111,0.00179787,30],[6735715,1494853431445,-100.99502222,0.00179787,30],[6735714,1494853431426,-2039.12850002,0.00179,30],[6735713,1494853431405,-183.88803906,0.00179,30],[6735711,1494853430206,-1855.24046096,0.00179,30],[6735710,1494853430193,-53.4042344,0.00152855,2],[6735709,1494853430181,-53.4042344,0.00152855,30],[6735708,1494853430165,-52.02165252,0.00093527,30],[6735707,1494853430150,-499.56735807,0.00093527,15],[6735706,1494853430137,-818.34204076,0.00093527,30],[6735705,1494853430123,-195.62001798,0.00093405,2],[6735695,1494853428508,-37.16518202,0.00093405,2],[6735688,1494853141068,-124.34634957,0.00178999,30],[6735687,1494853140987,-50.48604426,0.0017899,30],[6735686,1494853140978,-50.48604426,0.0017899,30],[6735685,1494853140967,-50.48604426,0.0017898,30],[6735684,1494853140957,-51.25765085,0.00160388,30],[6735683,1494853140914,-50.48604426,0.00160378,30],[6735682,1494853140813,-50.48604426,0.00160378,30],[6735681,1494853140801,-50.48604426,0.00160378,30],[6735680,1494853140790,-49.47973402,0.00160378,30],[6735679,1494853140761,-1.00631024,0.00160378,30],[6735678,1494853140748,-50.48604426,0.00160378,30],[6735677,1494853140726,-373.08499213,0.00160085,2],[6735676,1494853140716,-373.08499213,0.00160085,2],[6735675,1494853140702,-373.08499213,0.00160085,2],[6735674,1494853140690,-373.08499213,0.00160085,2],[6735673,1494853140679,-373.08499213,0.00160085,2],[6735672,1494853140664,-373.08499213,0.00160085,2],[6735671,1494853140652,-373.08499213,0.00160085,2],[6735670,1494853140633,-373.08499213,0.00160085,2],[6735669,1494853140617,-373.08499213,0.00160085,2],[6735668,1494853140603,-373.08499213,0.00160085,2],[6735667,1494853140591,-373.08499213,0.00160085,2],[6735666,1494853140577,-373.08499213,0.00160085,2],[6735665,1494853140564,-373.08499213,0.00160085,2],[6735664,1494853140551,-373.08499213,0.00160085,2],[6735663,1494853140532,-373.08499213,0.00160085,2],[6735662,1494853140513,-70.43,0.00158165,2],[6735661,1494853140500,-50.48604426,0.00142968,30],[6735660,1494853140486,-50.48604426,0.00142968,30],[6735659,1494853140468,-50.48604426,0.00142968,30],[6735658,1494853140456,-50.48604426,0.00142968,30],[6735657,1494853140445,-182.48,0.00140436,30],[6735656,1494853140432,-2462.69,0.00140436,30],[6735655,1494853140417,-1480.11391365,0.00125,30],[6735654,1494853140399,-50.48604426,0.0012499,30],[6735653,1494853140378,-50.48604426,0.0012499,30],[6735652,1494853140363,-51.25765085,0.0012,30],[6735651,1494853140345,-3740.90039169,0.0012,30],[6735650,1494853140330,-245.20955164,0.0012,30],[6735649,1494853140316,-117.84692501,0.0012,2],[6735648,1494853140300,-240.40288527,0.00119999,30],[6735647,1494853140286,-74.42092493,0.0011999,30],[6735646,1494853140273,-171.64801902,0.0011999,30],[6735645,1494853140256,-50.48604426,0.0011999,30],[6735644,1494853140243,-50.48604426,0.0011999,30],[6735643,1494853140228,-50.48604426,0.0011999,30],[6735641,1494853140215,-50.48604426,0.0011999,30],[6735639,1494853140202,-50.48604426,0.0011999,30],[6735636,1494853140181,-6508.92016589,0.00114717,30],[6735634,1494853140139,-566.49659196,0.00114717,30],[6735632,1494853140079,-0.82473213,0.00114717,30],[6735631,1494853139695,-324.49232558,0.00114717,30],[6735630,1494853139675,-26.64483104,0.00114717,30],[6735629,1494853139644,-895.94471993,0.00114717,30],[6735628,1494853139625,-571.02559163,0.00114717,30],[6735627,1494853139607,-739.67795707,0.00114714,2],[6735626,1494853139594,-2068,0.00107009,7],[6735625,1494853139577,-50.48604426,0.00093634,7],[6735624,1494853139563,-50.48604426,0.00093634,7],[6735623,1494853139549,-50.48604426,0.00093634,7],[6735622,1494853139534,-50.48604426,0.00093634,7],[6735621,1494853139521,-50.48604426,0.00093634,7],[6735620,1494853139507,-50.48604426,0.00093631,7],[6735619,1494853139489,-147.90174828,0.000936,14],[6735617,1494853139470,-51.25765085,0.00093589,30],[6735615,1494853139454,-50.48604426,0.00093584,7],[6735612,1494853139425,-455.25010385,0.00093567,2],[6735610,1494853139409,-56.21,0.00093567,2],[6735609,1494853139397,-700.99,0.00093567,30],[6735607,1494853139385,-95.94,0.00093567,30],[6735605,1494853139373,-50.48604426,0.00093557,7],[6735603,1494853139360,-50.48604426,0.00093557,7],[6735602,1494853139347,-56.07612898,0.00093507,30],[6735601,1494853139330,-51.25765085,0.00093507,30],[6735600,1494853139314,-51.25765085,0.00093507,30],[6735599,1494853139303,-51.25765085,0.00093507,30],[6735598,1494853139285,-538.77227604,0.00093505,5],[6735597,1494853139265,-68.7371996,0.00093505,5],[6735596,1494853139251,-274.37411766,0.00093505,2],[6735595,1494853139240,-3773.43737221,0.00093505,30],[6735594,1494853139227,-6283.68710181,0.00093505,30],[6735593,1494853139213,-7302.52088507,0.00093505,30],[6735592,1494853139192,-72.69342505,0.00093505,30],[6735591,1494853139175,-3670.70993709,0.00093505,30],[6735590,1494853139161,-710.97877232,0.00093505,5],[6735589,1494853139139,-1750.68218205,0.00093505,30],[6735588,1494853139120,-5917.26229886,0.00093505,30],[6735587,1494853139105,-4477.98100335,0.00093505,2],[6735586,1494853139088,-52.21529333,0.00093505,30],[6735585,1494853139073,-590.02,0.00093504,2],[6735584,1494853139056,-54.52,0.00093504,2]]
[[32179419,1494853783000,-0.04824433,1760.2],[32179413,1494853779000,0.03689378,1762.9],[32179410,1494853778000,-2,1763.1],[32179398,1494853774000,0.03694043,1763.4],[32179395,1494853772000,0.0170008,1763.4],[32179393,1494853768000,0.3134,1763.4],[32179392,1494853766000,0.1717,1763.4],[32179391,1494853765000,0.03674472,1763.4],[32179390,1494853765000,0.00009347,1763.4],[32179387,1494853763000,0.1717,1763.4],[32179385,1494853760000,0.5,1763.4],[32179384,1494853758000,0.2807,1763.4],[32179377,1494853756000,-0.248183,1762.9],[32179376,1494853754000,0.2582,1763.4],[32179375,1494853752000,-0.285902,1762.9],[32179374,1494853751000,0.21930653,1763.4],[32179373,1494853751000,0.28069347,1762.9],[32179372,1494853748000,0.5,1762.9],[32179371,1494853748000,0.01096553,1762.9],[32179359,1494853745000,0.1525,1762.9],[32179358,1494853731000,-0.101043,1762.7],[32179345,1494853700000,0.5,1762.9],[32179344,1494853698000,0.05,1763.4],[32179342,1494853696000,0.04711822,1763.3],[32179341,1494853696000,0.05288178,1762.8],[32179339,1494853695000,0.86873374,1762.8],[32179338,1494853694000,0.09,1762.8],[32179337,1494853693000,0.185,1762.8],[32179336,1494853690000,0.036,1762.8],[32179335,1494853689000,0.03595638,1762.8],[32179334,1494853689000,0.00004362,1762.3],[32179333,1494853689000,0.036,1762.3],[32179330,1494853686000,0.01868959,1762.3],[32179328,1494853686000,0.01868809,1762.3],[32179327,1494853686000,0.3,1762.3],[32179324,1494853685000,0.01868935,1762.3],[32179317,1494853685000,0.01868935,1762.3],[32179303,1494853683000,0.05,1762.3],[32179302,1494853681000,0.0721,1762.3],[32179301,1494853678000,0.1871,1762.3],[32179300,1494853676000,0.5,1762.3],[32179298,1494853673000,0.3,1762.3],[32179295,1494853663000,0.96,1762.4],[32179297,1494853663000,0.4070081,1762.8],[32179296,1494853663000,0.7534,1762.7],[32179294,1494853663000,0.24999,1762.4],[32179293,1494853663000,0.32344047,1762.3],[32179281,1494853657000,0.01616872,1762.3],[32179280,1494853657000,0.48020443,1762.2],[32179275,1494853656000,0.52038848,1762.2],[32179274,1494853656000,0.00830055,1761.7],[32179270,1494853651000,1.66872777,1761],[32179271,1494853651000,1.59169945,1761.7],[32179263,1494853649000,0.1742,1761],[32179262,1494853647000,0.4539,1761],[32179259,1494853645000,0.5,1761],[32179258,1494853644000,0.64862543,1761],[32179248,1494853643000,0.131,1761],[32179246,1494853642000,1.2488468,1761],[32179245,1494853641000,0.1747,1761],[32179243,1494853638000,1.00097535,1762],[32179242,1494853638000,0.1,1762],[32179241,1494853638000,0.00089345,1761.7],[32179240,1494853638000,0.39081416,1761.7],[32179236,1494853635000,0.5,1761.1],[32179234,1494853627000,0.079,1761.1],[32179233,1494853627000,0.421,1760],[32179232,1494853625000,0.0571,1760],[32179231,1494853621000,0.5,1760],[32179230,1494853619000,0.5,1760],[32179229,1494853618000,0.3587,1760],[32179228,1494853616000,0.5,1760],[32179227,1494853614000,0.5,1760],[32179226,1494853611000,0.5,1760],[32179225,1494853609000,0.5,1760],[32179224,1494853608000,0.5,1760],[32179223,1494853606000,0.5,1760],[32179222,1494853604000,0.076,1760],[32179221,1494853602000,0.0872,1760],[32179220,1494853594000,1.15228897,1760.4],[32179219,1494853594000,0.08998646,1760.2],[32179218,1494853594000,0.29275122,1760],[32179216,1494853593000,3.18635482,1760],[32179212,1494853568000,0.21838758,1760],[32179205,1494853562000,1.6,1759.9],[32179206,1494853562000,2.25250638,1760],[32179204,1494853562000,0.963884,1759.9],[32179203,1494853562000,1.2279,1759.8],[32179183,1494853513000,0.63707354,1759.2],[32179182,1494853513000,0.01209112,1758.4],[32179179,1494853508000,0.08790888,1758.4],[32179175,1494853499000,-0.92759884,1756.2],[32179172,1494853499000,-1.6,1756.8],[32179174,1494853499000,-0.9,1756.6],[32179173,1494853499000,-0.1,1756.6],[32179171,1494853499000,-1.23325116,1757.5],[32179170,1494853499000,-1.5752,1758.1],[32179169,1494853499000,-0.3882,1758.2],[32179168,1494853494000,0.01001354,1760.2],[32179166,1494853493000,-0.21,1759],[32179167,1494853493000,-0.33556,1757.9],[32179165,1494853493000,-0.7763,1759.1],[32179163,1494853489000,0.03756071,1760.3],[32179161,1494853487000,-0.32865189,1757.5],[32179160,1494853487000,-1.12193211,1757.7],[32179157,1494853487000,-0.1,1758.4],[32179158,1494853487000,-1.5033,1758],[32179159,1494853487000,-0.911706,1757.9],[32179155,1494853485000,0.02170315,1761.1],[32179149,1494853472000,0.5118,1761],[32179150,1494853472000,0.50505948,1761.1],[32179144,1494853468000,1.10829239,1761.7],[32179143,1494853468000,1.54315026,1761.1],[32179142,1494853468000,1.6273,1760.5],[32179141,1494853468000,0.1,1760.2],[32179140,1494853468000,1.71930627,1759.8],[32179138,1494853468000,0.08539655,1759.7],[32179139,1494853468000,0.92260345,1759.7],[32179124,1494853467000,-1.09071521,1756.8],[32179123,1494853467000,-0.02894644,1756.9]]
[ 13242,
[ [ 2578842316, 1967.5, 0.1 ],
[ 2578789721, 1967, 2 ],
[ 2578787950, 1966.7, 0.1 ],
[ 2578841323, 1966.7, 1.48 ],
[ 2578790926, 1966.5, 0.1 ],
[ 2578787536, 1966.3, 2.13425 ],
[ 2578782502, 1965.6, 2 ],
[ 2578824150, 1965.5, 0.1 ],
[ 2578754476, 1964.9, 0.1 ],
[ 2578782291, 1964.8, 2 ],
[ 2578781657, 1964.1, 2 ],
[ 2578781578, 1963.4, 2 ],
[ 2578758200, 1962.7, 0.1 ],
[ 2578759464, 1962.7, 2.2418413 ],
[ 2578757543, 1962, 2.24436613 ],
[ 2578842121, 1962, 0.43104651 ],
[ 2578838572, 1961.5, 2 ],
[ 2578757448, 1961.4, 2 ],
[ 2578757373, 1960.7, 2.0164 ],
[ 2578758228, 1960.7, 0.1 ],
[ 2578764149, 1960.2, 3 ],
[ 2578841514, 1960.2, 0.25 ],
[ 2578754697, 1959.9, 0.1 ],
[ 2578757246, 1959.9, 2.1 ],
[ 2578753035, 1959.3, 2.3016 ],
[ 2578842390, 1968.8, -0.07953231 ],
[ 2578842404, 1968.8, -0.1 ],
[ 2578842259, 1968.9, -0.8787 ],
[ 2578840418, 1969, -11.67581916 ],
[ 2578842168, 1969.7, -0.01 ],
[ 2578840772, 1969.8, -10 ],
[ 2578833728, 1969.9, -18.60507973 ],
[ 2578760844, 1970, -0.07251047 ],
[ 2578814935, 1970, -0.07606679 ],
[ 2578828061, 1970, -2 ],
[ 2578734028, 1970.7, -0.1 ],
[ 2578842236, 1970.9, -2.08080841 ],
[ 2578841867, 1971.5, -2.31 ],
[ 2578841774, 1972.3, -2.09 ],
[ 2578732893, 1972.7, -0.1 ],
[ 2578841607, 1972.9, -2.2 ],
[ 2578840675, 1973.6, -2.06821658 ],
[ 2578842101, 1973.9, -0.58625479 ],
[ 2578760447, 1974, -1 ],
[ 2578763766, 1974, -1 ],
[ 2578840619, 1974.3, -2.147368 ],
[ 2578798094, 1974.6, -0.01 ],
[ 2578731148, 1974.7, -0.1 ],
[ 2578734894, 1974.8, -49.9 ],
[ 2578420769, 1975, -0.02 ] ] ]
[300,[[1921.8,1,0.17974513],[1920.1,1,0.8809],[1920,4,3.61],[1919.2,1,1.4],[1919,2,1.09],[1918.4,1,1.58208707],[1918.1,2,1.05],[1918,2,1.03418],[1917.7,1,1.57],[1917.5,1,0.03],[1917,3,6.84188197],[1916.5,1,4.197819],[1916.4,1,1.6],[1916.2,1,0.1],[1915.7,1,1.58652],[1915,2,2.54],[1914.6,2,1.590214],[1914.5,2,0.43327161],[1914.3,1,0.1],[1914.2,2,1.580264],[1913.6,2,1.547905],[1913,1,0.012048],[1912.9,1,1.11],[1912.8,1,1.24],[1912.4,1,0.1],[1921.9,1,-5.12],[1922,2,-5.2353],[1922.4,1,-0.52759063],[1922.6,2,-1.271148],[1923,1,-1.4],[1923.4,1,-0.92932246],[1923.6,1,-5.2026],[1923.7,1,-1.98603296],[1923.8,1,-0.1],[1924,1,-1],[1924.5,1,-1.5],[1925.2,1,-1],[1925.6,1,-0.01],[1925.7,1,-0.1],[1925.8,1,-1.32],[1925.9,1,-1.4679],[1926.4,1,-0.62607888],[1926.5,1,-1.45],[1926.7,1,-6.2431],[1927,1,-1.74938207],[1927.3,1,-1.447971],[1927.6,1,-0.1],[1927.8,3,-2.58442],[1927.9,1,-1.6],[1928.3,1,-0.1]]]
[31,[[1779,1,42.11518492],[1776,1,0.65],[1775,6,4.08689264],[1774,5,4.426],[1773,5,5.50443213],[1772,6,7.79304654],[1771,4,7.1125],[1770,4,10.32053939],[1769,2,2.3227],[1768,3,2.3928],[1767,2,2.6782],[1766,5,2.29046402],[1765,4,8.4198058],[1764,3,4.1],[1763,3,2.36],[1762,5,27.5415973],[1761,3,1.319864],[1760,8,6.61289691],[1759,5,2.63016337],[1758,2,1.34098036],[1757,4,13.8345],[1756,4,6.23052701],[1755,2,1.24530005],[1754,4,6.049529],[1753,3,4.503684],[1780,14,-33.0779031],[1781,4,-1.58324806],[1782,2,-2.4],[1783,5,-4.17911621],[1784,12,-28.4504812],[1785,14,-9.47617966],[1786,12,-64.80149549],[1787,8,-13.94001992],[1788,10,-45.1987484],[1789,12,-21.28205024],[1790,11,-8.97454227],[1791,8,-12.36341796],[1792,10,-11.23215846],[1793,7,-26.68999992],[1794,6,-5.64348706],[1795,7,-63.0999998],[1796,9,-70.67113304],[1797,7,-14.99802176],[1798,13,-40.14042763],[1799,13,-28.45315875],[1800,23,-430.5491778],[1801,4,-2.56285988],[1802,5,-1.20007984],[1803,8,-2.46653968],[1804,5,-95.64373988]]]
[999,[[2567606289,1876.5,1.4305],[2567590859,1876.1,0.99670598],[2567602139,1876.1,1],[2567605297,1875.1,4.76123353],[2567605344,1875.1,0.01],[2567519477,1875,15],[2567552066,1875,0.01],[2567506309,1874.5,0.1],[2567603711,1874.4,1.820442],[2567477077,1874.2,0.19553],[2567477404,1874.2,0.07873206],[2567472587,1873.8,0.136159],[2567602057,1873.6,1.58],[2566800765,1873,1],[2567601717,1873,1.82726983],[2566876255,1872.6,0.0641877],[2567601259,1872.2,1.8],[2567048549,1871.7,0.01252505],[2567601253,1871.7,0.01252505],[2567606086,1871.7,1.50318748],[2566877752,1871.5,2.01],[2567606069,1871.5,4.8],[2567606105,1871,4.8],[2566874995,1870.9,2.0822],[2566863093,1870.7,0.1],[2567594844,1878.1,-0.42265059],[2567600874,1878.3,-0.1],[2567604766,1878.3,-1],[2567593507,1878.9,-1.662207],[2567598418,1879.5,-1.733],[2567588789,1879.6,-1.85096707],[2567593692,1880,-2],[2567564697,1880.1,-50],[2567564825,1880.2,-0.1],[2567587247,1880.3,-1.8071],[2567490789,1881,-1.59002156],[2567572064,1881,-5],[2567490713,1881.7,-1.79858828],[2567484859,1882.1,-0.1],[2567490678,1882.4,-2.001883],[2567601620,1882.9,-1.5],[2567484581,1883,-2],[2567546551,1883,-2],[2567606075,1883.4,-0.28613703],[2567603128,1883.5,-3.95],[2567474435,1883.7,-1.9],[2567504132,1883.7,-0.04646614],[2567504179,1883.7,-0.13263695],[2567403182,1884,-0.1],[2567442532,1884.5,-1.9324]]]
[22,[0.00078458,0.00075,30,3045.59478528,0.0007825,2,2335880.06705868,-0.0000674,-0.0793,0.0007825,19326761.40360705,0,0]]
[31,[[32288059,1494971706000,-0.00042864,1773.9],[32288058,1494971706000,-0.1,1775.6],[32288057,1494971706000,-0.04470386,1775.8],[32288054,1494971696000,-0.34748184,1776],[32288052,1494971693000,-0.08015163,1777.4],[32288047,1494971690000,-0.01984837,1777.4],[32288044,1494971687000,-0.18976977,1775.7],[32288040,1494971686000,0.26486383,1780.3],[32288039,1494971686000,0.95246765,1779.7],[32288038,1494971686000,0.1,1779.2],[32288037,1494971686000,0.8906,1779.1],[32288036,1494971686000,0.915224,1778.6],[32288035,1494971686000,1.6989623,1778.5],[32288034,1494971686000,0.53929331,1778.3],[32288033,1494971686000,1.9835,1778.2],[32288032,1494971686000,0.60187396,1778.1],[32288031,1494971686000,0.05321495,1778.1],[32288030,1494971684000,-1.79759839,1775.8],[32288029,1494971684000,-0.12600507,1777.4],[32288025,1494971683000,-0.05321495,1777.4],[32288024,1494971682000,0.1,1777.4],[32288023,1494971682000,0.04471204,1777.1],[32288022,1494971682000,0.1,1775.6],[32288021,1494971682000,0.67606794,1775.5],[32288014,1494971682000,-0.05321495,1775.4],[32288011,1494971681000,0.01840201,1775.5],[32288007,1494971678000,-0.21410901,1775.2],[32288006,1494971678000,-0.01986347,1775.2],[32288005,1494971678000,-0.78074942,1775.2],[32288003,1494971677000,-0.1544203,1775.2]]]
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const BFX = require('../index')
const { RESTv1, RESTv2 } = require('bfx-api-node-rest')
const WSv1 = require('bfx-api-node-ws1')
const WSv2 = require('../lib/transports/ws2')
describe('BFX', () => {
it('should be loaded', () => {
assert.strictEqual(typeof BFX, 'function')
})
describe('constructor', () => {
it('throws on using the deprecated way to set options', () => {
assert.throws(() => new BFX(2, {}))
assert.throws(() => new BFX('dummy', 'dummy', 2))
})
})
describe('rest', () => {
it('throws an error if an invalid version is requested', () => {
const bfx = new BFX()
assert.throws(bfx.rest.bind(bfx, 0))
assert.throws(bfx.rest.bind(bfx, 3))
})
it('returns correct REST api by version', () => {
const bfx = new BFX()
const restDefault = bfx.rest()
const rest1 = bfx.rest(1)
const rest2 = bfx.rest(2)
assert(restDefault instanceof RESTv2)
assert(rest1 instanceof RESTv1)
assert(rest2 instanceof RESTv2)
})
it('passes API keys & transform flag to new transport', () => {
const bfx = new BFX({
apiKey: 'k',
apiSecret: 's',
transform: true,
rest: {
url: 'http://'
}
})
const rest1 = bfx.rest(1)
const rest2 = bfx.rest(2)
assert.strictEqual(rest1._apiKey, 'k')
assert.strictEqual(rest2._apiKey, 'k')
assert.strictEqual(rest1._apiSecret, 's')
assert.strictEqual(rest2._apiSecret, 's')
assert.strictEqual(rest1._url, 'http://')
assert.strictEqual(rest2._url, 'http://')
assert.strictEqual(rest2._transform, true)
})
it('passes extra options to new transport', () => {
const bfx = new BFX()
const rest2 = bfx.rest(2, { url: '/dev/null' })
assert.strictEqual(rest2._url, '/dev/null')
})
it('returns one instance if called twice for the same version', () => {
const bfx = new BFX()
const restA = bfx.rest(2)
const restB = bfx.rest(2)
assert(restA === restB)
})
})
describe('ws', () => {
it('throws an error if an invalid version is requested', () => {
const bfx = new BFX()
assert.throws(bfx.ws.bind(bfx, 0))
assert.throws(bfx.ws.bind(bfx, 3))
})
it('returns correct WebSocket api by version', () => {
const bfx = new BFX()
const wsDefault = bfx.ws()
const ws1 = bfx.ws(1)
const ws2 = bfx.ws(2)
assert(wsDefault instanceof WSv2)
assert(ws1 instanceof WSv1)
assert(ws2 instanceof WSv2)
})
it('passes API keys & transform flag to new transport', () => {
const bfx = new BFX({
apiKey: 'k',
apiSecret: 's',
transform: true,
ws: {
url: 'wss://'
}
})
const ws1 = bfx.ws(1)
const ws2 = bfx.ws(2)
assert.strictEqual(ws1._apiKey, 'k')
assert.strictEqual(ws2._authArgs.apiKey, 'k')
assert.strictEqual(ws1._apiSecret, 's')
assert.strictEqual(ws2._authArgs.apiSecret, 's')
assert.strictEqual(ws1._url, 'wss://')
assert.strictEqual(ws2._url, 'wss://')
assert.strictEqual(ws2._transform, true)
})
it('passes extra options to new transport', () => {
const bfx = new BFX()
const ws2 = bfx.ws(2, { url: '/dev/null' })
assert.strictEqual(ws2._url, '/dev/null')
})
it('returns one instance if called twice for the same version', () => {
const bfx = new BFX()
const wsA = bfx.ws(2)
const wsB = bfx.ws(2)
assert(wsA === wsB)
})
})
})
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const WSv2 = require('../../../lib/transports/ws2')
const API_KEY = 'dummy'
const API_SECRET = 'dummy'
const createTestWSv2Instance = (params = {}) => {
return new WSv2({
apiKey: API_KEY,
apiSecret: API_SECRET,
url: 'ws://localhost:9997',
...params
})
}
describe('WSv2 channels', () => {
it('numeric and string channel ids work', () => {
const ws = createTestWSv2Instance()
ws._channelMap = {
83297: {
event: 'subscribed',
channel: 'book',
chanId: 83297,
symbol: 'tADAUSD',
prec: 'P0',
freq: 'F0',
len: '25',
pair: 'ADAUSD'
}
}
assert.strictEqual(ws.hasChannel(83297), true)
assert.strictEqual(ws.hasChannel('83297'), true)
assert.strictEqual(ws.hasChannel('1337'), false)
assert.strictEqual(ws.hasChannel(1337), false)
})
})
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const Promise = require('bluebird')
const WSv2 = require('../../../lib/transports/ws2')
const { Order } = require('bfx-api-node-models')
const { MockWSv2Server } = require('bfx-api-mock-srv')
const API_KEY = 'dummy'
const API_SECRET = 'dummy'
const createTestWSv2Instance = (params = {}) => {
return new WSv2({
apiKey: API_KEY,
apiSecret: API_SECRET,
url: 'ws://localhost:9997',
...params
})
}
describe('WSv2 integration', () => {
let ws = null
let wss = null
afterEach(async () => {
try { // may fail due to being modified by a test, it's not a problem
if (ws && ws.isOpen()) {
await ws.close()
}
} catch (e) {
assert.ok(true)
}
if (wss && wss.isOpen()) {
await wss.close()
}
ws = null
wss = null
})
describe('orders', () => {
it('creates & confirms orders', async () => {
wss = new MockWSv2Server({ listen: true })
ws = createTestWSv2Instance()
await ws.open()
await ws.auth()
const o = new Order({
gid: null,
cid: 0,
type: 'EXCHANGE LIMIT',
price: 100,
amount: 1,
symbol: 'tBTCUSD'
})
return ws.submitOrder(o)
})
it('keeps orders up to date', async () => {
wss = new MockWSv2Server({ listen: true })
ws = createTestWSv2Instance()
await ws.open()
await ws.auth()
const o = new Order({
gid: null,
cid: 0,
type: 'EXCHANGE LIMIT',
price: 100,
amount: 1,
symbol: 'tBTCUSD'
}, ws)
o.registerListeners()
await o.submit()
const arr = o.serialize()
arr[16] = 256
wss.send([0, 'ou', arr])
await Promise.delay(100)
assert.strictEqual(o.price, 256)
arr[16] = 150
wss.send([0, 'oc', arr])
await Promise.delay(100)
assert.strictEqual(o.price, 150)
o.removeListeners()
})
it('updateOrder: sends order changeset packet through', async () => {
wss = new MockWSv2Server()
ws = createTestWSv2Instance()
await ws.open()
await ws.auth()
let sawMessage = false
const o = new Order({
id: Date.now(),
type: 'EXCHANGE LIMIT',
price: 100,
amount: 1,
symbol: 'tBTCUSD'
}, ws)
ws._ws.send = (msgJSON) => {
const msg = JSON.parse(msgJSON)
assert.strictEqual(msg[0], 0)
assert.strictEqual(msg[1], 'ou')
assert(msg[3])
assert.strictEqual(msg[3].id, o.id)
assert.strictEqual(+msg[3].delta, 1)
assert.strictEqual(+msg[3].price, 200)
sawMessage = true
}
o.update({ price: 200, delta: 1 }) // note promise ignored
assert(sawMessage)
})
it('sends individual order packets when not buffering', async () => {
wss = new MockWSv2Server()
ws = createTestWSv2Instance()
await ws.open()
await ws.auth()
let sawBothOrders = false
const oA = new Order({
gid: null,
cid: Date.now(),
type: 'EXCHANGE LIMIT',
price: 100,
amount: 1,
symbol: 'tBTCUSD'
})
const oB = new Order({
gid: null,
cid: Date.now(),
type: 'EXCHANGE LIMIT',
price: 10,
amount: 1,
symbol: 'tETHUSD'
})
let sendN = 0
ws._ws.send = (msgJSON) => {
const msg = JSON.parse(msgJSON)
assert.strictEqual(msg[1], 'on')
sendN++
if (sendN === 2) {
sawBothOrders = true
}
}
// note promises ignored
ws.submitOrder(oA)
ws.submitOrder(oB)
assert(sawBothOrders)
})
it('buffers order packets', async () => {
wss = new MockWSv2Server()
ws = createTestWSv2Instance({
orderOpBufferDelay: 100
})
await ws.open()
await ws.auth()
const oA = new Order({
gid: null,
cid: Date.now(),
type: 'EXCHANGE LIMIT',
price: 100,
amount: 1,
symbol: 'tBTCUSD'
})
const oB = new Order({
gid: null,
cid: Date.now(),
type: 'EXCHANGE LIMIT',
price: 10,
amount: 1,
symbol: 'tETHUSD'
})
return new Promise((resolve) => {
ws._ws.send = (msgJSON) => {
const msg = JSON.parse(msgJSON)
assert.strictEqual(msg[1], 'ox_multi')
msg[3].forEach((payload) => {
assert.strictEqual(payload[0], 'on')
})
wss.close()
resolve()
}
// note promises ignored
ws.submitOrder(oA)
ws.submitOrder(oB)
})
})
})
describe('listeners', () => {
it('manages listeners by cbGID', () => {
ws = createTestWSv2Instance()
ws._channelMap = { 0: { channel: 'auth' } }
let updatesSeen = 0
ws.onAccountTradeUpdate({ pair: 'BTCUSD', cbGID: 10 }, () => updatesSeen++)
ws.onOrderUpdate({ symbol: 'tBTCUSD', cbGID: 10 }, () => updatesSeen++)
ws._handleChannelMessage([0, 'tu', [123, 'tBTCUSD']])
ws._handleChannelMessage([0, 'ou', [0, 0, 0, 'tBTCUSD']])
ws.removeListeners(10)
ws._handleChannelMessage([0, 'tu', [123, 'tBTCUSD']])
ws._handleChannelMessage([0, 'ou', [0, 0, 0, 'tBTCUSD']])
assert.strictEqual(updatesSeen, 2)
})
it('tracks channel refs to auto sub/unsub', async () => {
ws = createTestWSv2Instance()
wss = new MockWSv2Server()
let subs = 0
let unsubs = 0
await ws.open()
wss.on('message', (ws, msg) => {
if (msg.event === 'subscribe' && msg.channel === 'trades') {
subs++
ws.send(JSON.stringify({
event: 'subscribed',
chanId: 42,
channel: 'trades',
symbol: msg.symbol
}))
} else if (msg.event === 'unsubscribe' && msg.chanId === 42) {
unsubs++
ws.send(JSON.stringify({
event: 'unsubscribed',
chanId: 42
}))
}
})
ws.subscribeTrades('tBTCUSD')
ws.subscribeTrades('tBTCUSD')
ws.subscribeTrades('tBTCUSD')
ws.on('subscribed', () => {
ws.unsubscribeTrades('tBTCUSD')
ws.unsubscribeTrades('tBTCUSD')
ws.unsubscribeTrades('tBTCUSD')
ws.unsubscribeTrades('tBTCUSD')
ws.unsubscribeTrades('tBTCUSD')
})
return new Promise((resolve) => {
ws.on('unsubscribed', () => {
assert.strictEqual(subs, 1)
assert.strictEqual(unsubs, 1)
resolve()
})
})
})
})
describe('info message handling', () => {
it('notifies listeners on matching code', () => {
let sawMaintenanceEnd = false
ws = new WSv2()
ws.onInfoMessage(WSv2.info.MAINTENANCE_END, () => {
sawMaintenanceEnd = true
})
ws._onWSMessage(JSON.stringify({
event: 'info',
code: WSv2.info.MAINTENANCE_START,
msg: ''
}))
ws._onWSMessage(JSON.stringify({
event: 'info',
code: WSv2.info.MAINTENANCE_END,
msg: ''
}))
assert(sawMaintenanceEnd)
})
})
})

Sorry, the diff of this file is too big to display

/* eslint-env mocha */
'use strict'
const assert = require('assert')
const { TradingTicker } = require('bfx-api-node-models')
const { isClass } = require('../../../lib/util')
describe('isClass', () => {
it('returns true for classes', () => {
assert(isClass(TradingTicker))
})
it('returns false for functions', () => {
assert(!isClass(() => {}))
})
it('returns false for class instances', () => {
const t = new TradingTicker()
assert(!isClass(t))
})
it('returns false for primitives', () => {
assert(!isClass(42))
assert(!isClass('42'))
assert(!isClass({}))
assert(!isClass([]))
})
})
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const { isSnapshot } = require('../../../lib/util')
describe('isSnapshot - detects snapshots by data structure', () => {
it('returns false for heartbeats', () => {
assert.strictEqual(isSnapshot(['hb']), false)
})
it('returns false simple lists (data updates)', () => {
assert.strictEqual(isSnapshot([1337]), false)
})
it('returns true for nested lists (snapshots)', () => {
assert.strictEqual(isSnapshot([['a'], ['b']]), true)
})
})
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const Promise = require('bluebird')
const _isObject = require('lodash/isObject')
const _isArray = require('lodash/isArray')
const WS2Manager = require('../../lib/ws2_manager')
const WSv2 = require('../../lib/transports/ws2')
describe('WS2Manager', () => {
let m
afterEach(async () => {
if (m) {
try {
await m.close()
} catch (e) {
assert.ok(true, 'may fail due to being modified internally')
} finally {
m = null // eslint-disable-line
}
}
})
describe('setAuthArgs', () => {
it('updates the internal auth args', () => {
m = new WS2Manager()
m.setAuthArgs({ apiKey: '42' })
assert.strictEqual(m.getAuthArgs().apiKey, '42')
})
})
describe('getAuthArgs', () => {
it('returns internal auth args', () => {
m = new WS2Manager()
m.setAuthArgs({ apiKey: '42' })
assert.strictEqual(m.getAuthArgs().apiKey, '42')
})
})
describe('reconnect', () => {
it('calls reconnect on all sockets', async () => {
m = new WS2Manager()
let called = false
m._sockets.push({
ws: { reconnect: async () => { called = true } }
})
await m.reconnect()
assert.ok(called, 'reconnect not called on socket')
})
it('resolves when all sockets reconnect', async () => {
m = new WS2Manager()
let called = false
m._sockets.push({
ws: {
reconnect: async () => {
await Promise.delay(10)
called = true
}
}
})
await m.reconnect()
assert.ok(called, 'reconnect not called on socket')
})
})
describe('close', () => {
it('calls close on all sockets', async () => {
m = new WS2Manager()
let called = false
m._sockets.push({
ws: { close: async () => { called = true } }
})
await m.close()
assert.ok(called, 'close not called on socket')
})
it('resolves when all sockets close', async () => {
m = new WS2Manager()
let called = false
m._sockets.push({
ws: {
close: async () => {
await Promise.delay(10)
called = true
}
}
})
await m.close()
assert.ok(called, 'close not called on socket')
})
})
describe('getNumSockets', () => {
it('returns the number of sockets', () => {
m = new WS2Manager()
m._sockets.push({})
m._sockets.push({})
assert.strictEqual(m.getNumSockets(), 2, 'did not report correct number of sockets')
})
})
describe('getSocket', () => {
it('returns the socket at the requested index', () => {
m = new WS2Manager()
m._sockets.push(1)
m._sockets.push(42)
assert.strictEqual(m.getSocket(1), 42)
})
})
describe('getSocketInfo', () => {
it('returns an array of objects reporting number of data channels per socket', () => {
m = new WS2Manager()
m._sockets.push({
pendingSubscriptions: [[], [], []],
pendingUnsubscriptions: [[]],
ws: { getDataChannelCount: () => 2 }
})
m._sockets.push({
pendingSubscriptions: [[], [], []],
pendingUnsubscriptions: [[]],
ws: { getDataChannelCount: () => 3 }
})
const info = m.getSocketInfo()
assert.ok(_isArray(info), 'did not return array')
info.forEach(i => assert.ok(_isObject(i), 'socket info not an object'))
assert.strictEqual(info[0].nChannels, 4, 'socket info does not report correct number of channels')
assert.strictEqual(info[1].nChannels, 5, 'socket info does not report correct number of channels')
})
})
describe('getDataChannelCount', () => {
it('takes pending subs & unsubs into account', () => {
const s = {
ws: new WSv2(),
pendingSubscriptions: [['book', {}]],
pendingUnsubscriptions: []
}
s.ws._channelMap = {
0: { channel: 'trades' },
1: { channel: 'candles', key: 'test' },
2: { channel: 'auth' }
}
const count = WS2Manager.getDataChannelCount(s)
assert.strictEqual(s.ws.getDataChannelCount(), 2)
assert.strictEqual(count, 3)
})
})
describe('auth', () => {
it('does nothing if api key/secret are already provided', () => {
m = new WS2Manager({ apiKey: 'x', apiSecret: 'x' })
m.auth({ apiKey: '42', apiSecret: '43' })
assert.strictEqual(m._socketArgs.apiKey, 'x')
assert.strictEqual(m._socketArgs.apiSecret, 'x')
})
it('saves auth args', () => {
m = new WS2Manager()
m.auth({ calc: 1, dms: 4 })
assert.strictEqual(m._authArgs.calc, 1)
assert.strictEqual(m._authArgs.dms, 4)
})
it('calls auth on existing unauthenticated sockets', (done) => {
let cred = false
m = new WS2Manager()
m._sockets = [{
ws: {
isAuthenticated: () => false,
updateAuthArgs: ({ apiKey: key, apiSecret: secret }) => { cred = `${key}:${secret}` },
auth: () => {
assert.strictEqual(cred, '41:42')
done()
}
}
}]
m.auth({ apiKey: '41', apiSecret: '42' })
})
})
describe('openSocket', () => {
it('binds listeners to forward events', async () => {
const heardEvents = {}
const events = [
'open', 'message', 'auth', 'error', 'close', 'subscribed',
'unsubscribed'
]
m = new WS2Manager()
const s = m.openSocket()
const { ws } = s
events.forEach(e => {
m.on(e, () => { heardEvents[e] = true })
})
events.forEach(e => ws.emit(e))
events.forEach(e => {
assert(heardEvents[e])
})
return new Promise((resolve, reject) => {
ws.on('open', () => ws.close().then(resolve).catch(reject))
})
}).timeout(4000)
it('saves socket state', async () => {
m = new WS2Manager()
const s = m.openSocket()
const { ws } = s
assert.deepStrictEqual(m._sockets[0], s)
return new Promise((resolve, reject) => {
ws.on('open', () => ws.close().then(resolve).catch(reject))
})
}).timeout(4000)
it('binds \'unsubscribed\' listener to remove channel from pending unsubs', async () => {
m = new WS2Manager()
const s = m.openSocket()
const { ws } = s
s.pendingUnsubscriptions.push(`${42}`)
s.ws.emit('unsubscribed', { chanId: 42 })
assert.strictEqual(s.pendingUnsubscriptions.length, 0)
return new Promise((resolve, reject) => {
ws.on('open', () => ws.close().then(resolve).catch(reject))
})
}).timeout(4000)
it('binds \'subscribed\' listener to remove channel from pending subs', async () => {
m = new WS2Manager()
const s = m.openSocket()
const { ws } = s
s.pendingSubscriptions.push(['book', { symbol: 'tBTCUSD', prec: 'R0' }])
s.ws.emit('subscribed', {
channel: 'book',
symbol: 'tBTCUSD',
prec: 'R0',
len: '25'
})
assert.strictEqual(s.pendingSubscriptions.length, 0)
return new Promise((resolve, reject) => {
ws.on('open', () => ws.close().then(resolve).catch(reject))
})
}).timeout(4000)
it('auto-auths if manager has credentials configured', (done) => {
m = new WS2Manager({
apiKey: 'key',
apiSecret: 'secret'
})
const s = m.openSocket()
const { ws } = s
ws.auth = async () => {
assert.strictEqual(ws._authArgs.apiKey, 'key', 'api key not set')
assert.strictEqual(ws._authArgs.apiSecret, 'secret', 'api secret not set')
await ws.close()
done()
}
}).timeout(4000)
})
describe('getAuthenticatedSocket', () => {
it('returns the first authenticated socket found', () => {
m = new WS2Manager()
for (let i = 0; i < 3; i += 1) {
m._sockets.push({
test: i,
ws: { isAuthenticated: () => i === 1 }
})
}
assert.strictEqual(m.getAuthenticatedSocket().test, 1, 'did not return correct socket')
})
})
describe('getFreeDataSocket', () => {
it('returns the first socket below the data channel limit', () => {
m = new WS2Manager()
m._sockets[0] = {
ws: { getDataChannelCount: () => 200 },
pendingSubscriptions: new Array(70),
pendingUnsubscriptions: new Array(10)
}
m._sockets[1] = {
ws: { getDataChannelCount: () => 5 },
pendingSubscriptions: [],
pendingUnsubscriptions: []
}
const s = m.getFreeDataSocket()
assert.deepStrictEqual(s, m._sockets[1])
})
})
describe('getSocketWithDataChannel', () => {
it('returns socket subscribed to specified channel/filter pair', () => {
m = new WS2Manager()
m._sockets[0] = {
ws: {},
pendingSubscriptions: [['candles', { key: 'test' }]],
pendingUnsubscriptions: []
}
let s = m.getSocketWithDataChannel('candles', { key: 'test' })
assert.deepStrictEqual(s, m._sockets[0])
/// /
m._sockets[0] = {
ws: { getDataChannelId: () => false },
pendingSubscriptions: [['auth', {}]],
pendingUnsubscriptions: []
}
s = m.getSocketWithDataChannel('candles', { key: 'test' })
assert(!s)
/// /
m._sockets[0] = {
ws: {
getDataChannelId: (type, filter) => {
assert.strictEqual(type, 'candles')
assert.deepStrictEqual(filter, { key: 'test' })
return 1
}
},
pendingSubscriptions: [],
pendingUnsubscriptions: []
}
s = m.getSocketWithDataChannel('candles', { key: 'test' })
assert.deepStrictEqual(s, m._sockets[0])
/// /
m._sockets[0] = {
ws: {
getDataChannelId: (type, filter) => {
assert.strictEqual(type, 'candles')
assert.deepStrictEqual(filter, { key: 'test' })
return 1
}
},
pendingSubscriptions: [],
pendingUnsubscriptions: [1]
}
s = m.getSocketWithDataChannel('candles', { key: 'test' })
assert(!s)
})
})
describe('getSocketWithChannel', () => {
it('returns correct socket', () => {
m = new WS2Manager()
m._sockets[0] = {
pendingUnsubscriptions: [],
ws: {
hasChannel: (id) => {
return id === 42
}
}
}
let s = m.getSocketWithChannel(42)
assert.deepStrictEqual(s, m._sockets[0])
/// /
m._sockets[0] = {
pendingUnsubscriptions: [42],
ws: {
hasChannel: (id) => {
return id === 42
}
}
}
s = m.getSocketWithChannel(42)
assert(!s)
})
})
describe('getSocketWithSubRef', () => {
it('returns the first socket found that has the requested subscription ref', () => {
m = new WS2Manager()
for (let i = 0; i < 3; i += 1) {
m._sockets.push({
test: i,
ws: {
hasSubscriptionRef: (channel, identifier) => {
assert.strictEqual(channel, 'a', 'did not pass channel through')
assert.strictEqual(identifier, 'b', 'did not pass identifier through')
return i === 1
}
}
})
}
const s = m.getSocketWithSubRef('a', 'b')
assert.ok(_isObject(s), 'did not return a socket')
assert.strictEqual(s.test, 1, 'did not return correct socket')
})
})
describe('subscribe', () => {
it('delays sub for unopened sockets', () => {
m = new WS2Manager()
let onceOpenCalled = false
m._sockets[0] = {
pendingSubscriptions: [],
pendingUnsubscriptions: [],
ws: {
getDataChannelCount: () => 0,
managedSubscribe: () => assert(false),
isOpen: () => false,
once: (eName) => {
assert.strictEqual(eName, 'open')
onceOpenCalled = true
}
}
}
m.subscribe('candles', 'test', { key: 'test' })
assert(onceOpenCalled)
})
it('saves pending sub', () => {
m = new WS2Manager()
m._sockets[0] = {
pendingSubscriptions: [],
pendingUnsubscriptions: [],
ws: {
getDataChannelCount: () => 0,
managedSubscribe: () => {},
isOpen: () => true
}
}
m.subscribe('candles', 'test', { key: 'test' })
assert.deepStrictEqual(m._sockets[0].pendingSubscriptions, [
['candles', { key: 'test' }]
])
})
it('opens a new socket if no sockets are available', () => {
m = new WS2Manager()
let openCalled = false
m.openSocket = () => {
openCalled = true
return {
pendingSubscriptions: [],
ws: {
once: () => {},
isOpen: () => false // to avoid managed sub
}
}
}
m.subscribe('candles', 'test', { key: 'test' })
assert(openCalled)
})
it('opens a new socket if no sockets are below data limit', () => {
m = new WS2Manager()
let openCalled = false
m._sockets[0] = {
pendingSubscriptions: [],
pendingUnsubscriptions: [],
ws: {
getDataChannelCount: () => 255
}
}
m.openSocket = () => {
openCalled = true
const state = {
pendingSubscriptions: [],
ws: {
once: () => {},
isOpen: () => false // to avoid managed sub
}
}
m._sockets.push(state)
return state
}
m.subscribe('candles', 'test', { key: 'test' })
assert(openCalled)
assert.strictEqual(m._sockets.length, 2)
})
})
describe('unsubscribe', () => {
it('saves pending unsub & calls unsub on socket', () => {
m = new WS2Manager()
let unsubCalled = false
m._sockets[0] = {
pendingUnsubscriptions: [],
ws: {
unsubscribe: (cid) => {
assert.strictEqual(cid, 42)
unsubCalled = true
},
hasChannel: (cid) => {
return cid === 42
}
}
}
m.unsubscribe(42)
assert.deepStrictEqual(m._sockets[0].pendingUnsubscriptions, [42])
assert(unsubCalled)
})
})
describe('managedUnsubscribe', () => {
it('saves pending unsub and calls managed unsub on socket', () => {
m = new WS2Manager()
let unsubCalled = false
m._sockets[0] = {
pendingUnsubscriptions: [],
ws: {
managedUnsubscribe: (cid) => {
assert.strictEqual(cid, 42)
unsubCalled = true
},
hasSubscriptionRef: (cid) => cid === 42,
_chanIdByIdentifier: () => 42
}
}
m.managedUnsubscribe(42)
assert.deepStrictEqual(m._sockets[0].pendingUnsubscriptions, [42])
assert(unsubCalled)
})
})
describe('withAllSockets', () => {
it('calls the provided cb with all internal sockets', () => {
m = new WS2Manager()
const socketsSeen = {}
m._sockets = ['a', 'b', 'c']
m.withAllSockets((sock) => {
socketsSeen[sock] = true
})
assert(socketsSeen.a)
assert(socketsSeen.b)
assert(socketsSeen.c)
})
})
describe('subscribeOrderBook', () => {
it('calls subscribe with a valid filter and the provided symbol', (done) => {
m = new WS2Manager()
m.subscribe = (type, symbol, filter) => {
assert.ok(_isObject(filter), 'filter not an object')
assert.strictEqual(filter.symbol, 'tBTCUSD', 'symbol did not match')
assert.strictEqual(filter.prec, 'P0', 'prec did not match')
assert.strictEqual(filter.len, '25', 'len did not match')
assert.strictEqual(filter.freq, 'F0', 'freq did not match')
assert.strictEqual(symbol, 'tBTCUSD')
done()
}
m.subscribeOrderBook('tBTCUSD', 'P0', '25', 'F0')
})
})
describe('onOrderBook', () => {
it('passes a valid OB filter to the first socket with a book channel', (done) => {
const assertFilter = (filter) => {
assert.ok(_isObject(filter), 'filter not an object')
assert.strictEqual(filter.symbol, 'tBTCUSD', 'symbol did not match')
assert.strictEqual(filter.prec, 'P0', 'prec did not match')
assert.strictEqual(filter.len, '25', 'len did not match')
assert.strictEqual(filter.freq, 'F0', 'freq did not match')
}
m = new WS2Manager()
m._sockets.push({
pendingSubscriptions: [],
pendingUnsubscriptions: [],
ws: {
getDataChannelId: (type, filter) => {
assert.strictEqual(type, 'book')
assertFilter(filter)
return 42
},
onOrderBook: (filter) => {
assertFilter(filter)
done()
}
}
})
m.onOrderBook({
symbol: 'tBTCUSD',
prec: 'P0',
len: '25',
freq: 'F0'
})
})
})
})

Sorry, the diff of this file is too big to display