CCXT reinstalled

This commit is contained in:
Luis Fernando Molina committed 2020-12-03 12:08:44 +01:00
1 parent 21be1616ee
commit b533bb3bb6
274 files changed
+79395 -30705

No files matched your search

+7 -7
View File
@@ -646,13 +646,6 @@
"fileName": "Snapshots.js",
"folderName": "Trading-Bot/Low-Frequency-Trading"
},
{
"name": "Exchange API",
"propertyName": "exchangeAPI",
"functionName": "newSuperalgosBotModulesExchangeAPI",
"fileName": "ExchangeAPI.js",
"folderName": "Trading-Bot/Low-Frequency-Trading"
},
{
"name": "Trading Orders",
"propertyName": "tradingOrders",
@@ -673,6 +666,13 @@
"functionName": "newSuperalgosBotModulesOrdersCalculations",
"fileName": "OrdersCalculations.js",
"folderName": "Trading-Bot/Low-Frequency-Trading"
},
{
"name": "Exchange API",
"propertyName": "exchangeAPI",
"functionName": "newSuperalgosBotModulesExchangeAPI",
"fileName": "ExchangeAPI.js",
"folderName": "Trading-Bot/Low-Frequency-Trading/APIs"
}
]
},
@@ -0,0 +1,6 @@
.git
.dockerignore
Dockerfile
node_modules
*.swp
*.pyc
@@ -0,0 +1,4 @@
*.* text eol=lf
php/test/*.php linguist-generated=true
python/ccxt/*.py linguist-generated=true
python/ccxt/__init__.py linguist-generated=false
@@ -0,0 +1,926 @@
# Contributing To The CCXT Library
- [How To Submit A Question Or Issue](#how-to-submit-an-issue)
- [How To Contribute Code](#how-to-contribute-code)
- [What You Need To Have](#what-you-need-to-have)
- [What You Need To Know](#what-you-need-to-know)
## How To Submit An Issue
If you want to submit an issue and you want your issue to be resolved quickly, here's a checklist for you:
- Read the [Manual](https://github.com/ccxt/ccxt/wiki/Manual), and especially carefully read the following sections:
- [Exchange Properties](https://github.com/ccxt/ccxt/wiki/Manual#exchange-properties)
- [Rate Limit](https://github.com/ccxt/ccxt/wiki/Manual#rate-limit)
- [DDoS Protection](https://github.com/ccxt/ccxt/wiki/Manual#ddos-protection-by-cloudflare--incapsula)
- [Authentication](https://github.com/ccxt/ccxt/wiki/Manual#authentication)
- [API Keys Setup](https://github.com/ccxt/ccxt/wiki/Manual#api-keys-setup)
- Read the [Troubleshooting](https://github.com/ccxt/ccxt/wiki/Manual#troubleshooting) section and follow troubleshooting steps.
- Read the [FAQ](https://github.com/ccxt/ccxt/wiki/FAQ) for most frequently asked questions.
- Read the [API docs](https://github.com/ccxt/ccxt/wiki/Exchange-Markets) for your exchange.
- Search for similar issues first to avoid duplicates.
- If your issue is unique, along with a basic description of the failure, the following **IS REQUIRED**:
- **set `exchange.verbose = true` property on the exchange instance before calling its functions or methods**
- **DON'T POST SCREENSHOTS OF CODE OR ERRORS, POST THE OUTPUT AND CODE IN PLAIN TEXT!**
- **surround code and output with triple backticks: ```GOOD```**
- don't confuse the backtick symbol (`) with the quote symbol (\'): '''BAD'''
- don't confuse a single backtick with triple backticks: `BAD`
- paste a complete code snippet you're having difficulties with, avoid one-liners
- paste the **full verbose output** of the failing method without your keys
- the verbose output should include the request and response from the exchange (not just an error callstack)
- write your language **and version**
- write ccxt library version
- which exchange it is
- which method you're trying to call
### Reporting Vulnerabilities And Critical Issues
If you found a security issue or a critical vulnerability and reporting it in public would impose risk – please feel free to send us a message to <a href="mailto:info@ccxt.trade">info@ccxt.trade</a>.
## How To Contribute Code
- **[MAKE SURE YOUR CODE IS UNIFIED](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#derived-exchange-classes)!**
↑ This is the most important rule of all.
- **PLEASE, DO NOT COMMIT THE FOLLOWING FILES IN PULL REQUESTS:**
- `/doc/*` (these files are generated from `/wiki/*`, place your edits there)
- `/build/*` (these are generated automatically)
- `/php/*` (except for base classes)
- `/python/*` (except for base classes)
- `/ccxt.js`
- `/README.md` (exchange lists are generated automatically)
- `/package.json`
- `/package.lock`
- `/wiki/*` (except for real edits, exchange lists are generated automatically)
- `/dist/ccxt.browser.js` (this is also browserified automatically)
These files are generated ([explained below](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#multilanguage-support)) and will be overwritten upon build. Please don't commit them to avoid bloating the repository which is already quite large. Most often, you have to commit just one single source file to submit an edit to the implementation of an exchange.
- **PLEASE, SUBMIT ATOMIC EDITS, ONE PULL REQUEST PER ONE EXCHANGE, DO NOT MIX THEM**
- **MAKE SURE YOUR CODE PASSES ALL SYNTAX CHECKS BY RUNNING `npm run build`**
## Pending Tasks
Below is a list of functionality we would like to have implemented and fully **unified** in the library in the first place at this time. Most of these tasks are already in progress, implemented for some exchanges, but not all of them:
- Margin trading
- Leverage
- Derivatives (futures, options)
- Main account / subaccounts
- Conditional orders (stop loss, take profit)
- `transfer` between subaccounts and main account
- `fetchTransfer`
- `fetchTransfers`
- `fetchLedger`
- `fetchPositions`
- `closePosition`
- `closePositions`
If you want to contribute by submitting partial implementations be sure to look up examples of how it's done inside the library (where implemented already) and copy the adopted practices.
If your proposal, suggestion or improvement does not relate to the above list of tasks before submitting it make sure it is:
1. really needed by the majority of ccxt users
2. designed to be a general-purpose solution, not hardcoded for your specific needs
3. done in a generalized way compatible with all exchanges (not exchange-specific)
4. portable (available in all supported languages)
5. robust
6. explicit in what it's doing
7. doesn't break anything (if you change a method, make sure that all other methods calling the edited method are not broken)
The following is a set of rules for contributing to the ccxt library codebase.
## What You Need To Have
If you're not going to develop CCXT and contribute code to the CCXT library, then you don't need the Docker image nor the CCXT repository. If you just want to use CCXT inside your project simply install it as a regular package into the project folder as documented in the Manual (https://github.com/ccxt/ccxt/wiki/Install):
- [JavaScript / Node.js / NPM](https://github.com/ccxt/ccxt/wiki/Install#javascript-npm)
```shell
# JavaScript / Node.js / NPM
npm install ccxt
```
- [Python / PIP](https://github.com/ccxt/ccxt/wiki/Install#python)
```shell
# Python
pip install ccxt # or pip3 install ccxt
```
- [PHP / Composer](https://github.com/ccxt/ccxt/wiki/Install#php)
```shell
# PHP / Composer
composer install ccxt
```
### With Docker
The easiest way is to use Docker to run an isolated build & test enviroment with all the dependencies installed:
```shell
docker-compose run --rm ccxt
```
That builds a container and opens a shell, where the `npm run build` and `node run-tests` commands should simply work out of the box.
The CCXT folder is mapped inside of the container, except the `node_modules` folder — the container would have its own ephemeral copy — so that won't mess up your locally installed modules. This means that you can edit sources on your host machine using your favorite editor and build/test them in the running container.
This way you can keep the build tools and processes isolated, not having to work through the painful process of installing all those dependencies to your host machine manually.
### Without Docker
#### Dependencies
- Git
- [Node.js](https://nodejs.org/en/download/) 8+
- [Python](https://www.python.org/downloads/) 3.5.3+
- requests (`pip install requests`)
- [aiohttp](https://docs.aiohttp.org/) (`pip install aiohttp`)
- [tox](https://tox.readthedocs.io)
- via pip: `pip install tox`
- MacOS with [brew](https://brew.sh): `brew install tox`
- Ubuntu Linux: `apt-get install tox`
- [PHP](https://secure.php.net/downloads.php) 5.3+ with the following extensions installed and enabled:
- cURL
- iconv
- mbstring
- PCRE
- bcmath (php<7.1)
#### Build Steps
```shell
git clone https://github.com/ccxt/ccxt.git
```
```shell
cd ccxt
```
```shell
npm install
```
```shell
npm run build
```
## What You Need To Know
### Repository Structure
The contents of the repository are structured as follows:
```shell
/ # root directory aka npm module/package folder for Node.js
/.babelrc # babel config used for making the ES5 version of the library
/.eslintrc # linter
/.gitattributes # contains linguist settings for language detection in repo
/.gitignore # ignore it
/.npmignore # files to exclude from the NPM package
/.travis.yml # a YAML config for travis-ci (continuous integration)
/CONTRIBUTING.md # this file
/LICENSE.txt # MIT
/README.md # master markdown for GitHub, npmjs.com, npms.io, yarn and others
/build/ # build scripts
/build/export-exchanges.js # used to create tables of exchanges in the docs during the build
/build/transpile.js # the transpilation script
/build/update-badges.js # a JS script to update badges in the README and in docs
/build/vss.js # reads single-sourced version from package.json and writes it everywhere
/dist/ # a folder for the generated browser bundle of CCXT
/ccxt.js # entry point for the master JS version of the ccxt library
/ccxt.php # entry point for the PHP version of the ccxt library
/doc/ # Sphinx-generated rst-docs for http://ccxt.readthedocs.io/
/js/ # the JS version of the library
/php/ # PHP ccxt module/package folder
/python/ # Python ccxt module/package folder for PyPI
/python/__init__.py # entry point for the Python version of the ccxt.library
/python/async_support/ # asynchronous version of the ccxt.library for Python 3.5.3+ asyncio
/python/base/ # base code for the Python version of the ccxt library
/python/MANIFEST.in # a PyPI-package file listing extra package files (license, configs, etc...)
/python/README.md # a copy of README.md for PyPI
/python/setup.cfg # wheels config file for the Python package
/python/setup.py # pip/setuptools script (build/install) for ccxt in Python
/python/tox.ini # tox config for Python
/examples/ # self-explanatory
/examples/js # ...
/examples/php # ...
/examples/py # ...
/exchanges.cfg # custom bundle config for including only the exchanges you need
/package.json # npm package file, also used in setup.py for version single-sourcing
/run-tests.js # a front-end to run individual tests of all exchanges in all languages (JS/PHP/Python)
/wiki/ # the source of all docs (edits go here)
```
### Multilanguage Support
The ccxt library is available in three different languages (more to come). We encourage developers to design *portable* code, so that a single-language user could read the code in other languages and understand it easily. This helps the adoption of the library. The main goal is to provide a generalized, unified, consistent and robust interface to as many existing cryptocurrency exchanges as possible.
At first, all language-specific versions were developed in parallel, but separately from each other. But when it became too hard to maintain and keep the code consistent among all supported languages we have decided to switch to what we call a *source/generated* process. There is now a single source version in one language, that is JavaScript. Other language-specific versions are syntactically derived (transpiled, generated) automatically from the source version. But it doesn't mean that you have to be a JS coder to contribute. The portability principle allows Python and PHP devs to effectively participate in developing the source version as well.
The module entry points are:
- `./python/__init__.py` for the Python pip package
- `./python/async/__init__.py` for the Python 3.5.3+ ccxt.async_support subpackage
- `./ccxt.js` for the Node.js npm package
- `./dist/ccxt.browser.js` for the browser bundle
- `./ccxt.php` for PHP
Generated versions and docs are transpiled from the source `ccxt.js` file and files in `./js/` by the `npm run build` command.
### Transpiled (generated) files
- All derived exchange classes are transpiled automatically from source JS files. The source files are language-agnostic, easily mapped line-to-line to any other language and written in a cross-language-compatible way. Any coder can read it (by design).
- All base classes are **not** transpiled, those are language-specific.
#### JavaScript
The `ccxt.browser.js` is generated with Babel from source.
#### Python
These files containing derived exchange classes are transpiled from JS into Python:
- `js/[_a-z].js` → `python/ccxt/async/[_a-z].py`
- `python/ccxt/async[_a-z].py` → `python/ccxt/[_a-z].py` (Python 3 asyncio → Python 2 sync transpilation stage)
- `python/test/test_async.py` → `python/test/test.py` (the sync test is generated from the async test)
These Python base classes and files are not transpiled:
- `python/ccxt/base/*`
- `python/ccxt/async/base/*`
#### PHP
These files containing derived exchange classes are transpiled from JS into PHP:
- `js/[_a-z].js` → `php/[_a-z].php`
These PHP base classes and files are not transpiled:
- `php/base/*`
#### Typescript
- `js/[_a-z].js` → `ccxt.d.ts`
### Base Class
```UNDER CONSTRUCTION```
### Derived Exchange Classes
Transpiler is regex-based and heavily relies on specific formatting rules. If you break them then the transpiler will either
fail to generate Python/PHP classes at all or will generate malformed Python/PHP syntax.
Below are key notes on how to keep the JS code transpileable.
Use the linter `npm run lint js/your-exchange-implementation.js` before you build. It will cover many (but not all) the issues,
so manual checking will still be required if transpilation fails.
If you see a `[TypeError] Cannot read property '1' of null` exception or any other transpilation error when you `npm run build`, check if your code satisfies the following rules:
- don't put empty lines inside your methods
- always use Python-style indentation, it is preserved as is for all languages
- indent with 4 spaces **exactly**, avoid tabs
- put an empty line between each of your methods
- avoid mixed comment styles, use double-slash `//` in JS for line comments
- avoid multi-line comments
If the transpiling process finishes successfully, but generates incorrect Python/PHP syntax, check for the following:
- every opening bracket like `(` or `{` should have a space before it!
- do not use language-specific code syntax sugar, even if you really want to
- unfold all maps and comprehensions to basic for-loops
- don't change the arguments of overridden inherited methods, keep them uniform across all exchanges
- do everything with base class methods only (for example, use `this.json ()` for converting objects to json).
- always put a semicolon `;` at the end of each statement, as in PHP/C-style
- all associative keys must be single-quoted strings everywhere, `array['good'], array.bad`
- variables should be declared with `const` or `let` keywords semantically (no `var`!), prefer `const` everywhere
And structurally:
- if you need another base method you will have to implement it in all three languages
- do not issue more than one HTTP request from a unified method
- try to reduce syntax to basic one-liner expressions
- multiple lines are ok, but you should avoid deep nesting with lots of brackets
- avoid changing the contents of the arguments and params passed by reference into function calls
- do not use conditional statements that are too complex (heavy if-bracketing)
- do not use heavy ternary conditionals
- avoid operators clutter (**don't do this**: `a && b || c ? d + 80 : e ** f`)
- do not use `.includes()`, use `.indexOf()` instead!
- never use `.toString()` on floats: `Number (0.00000001).toString () === '1e-8'`
- do not use closures, `a.map` or `a.filter (x => (x === 'foobar'))` is not acceptable in derived classes
- do not use the `in` operator to check if a value is in a non-associative array (list)
- don't add custom currency or symbol/pair conversions and formatting, copy from existing code instead
- **don't access non-existent keys, `array['key'] || {}` won't work in other languages!**
- keep it simple, don't do more than one statement in one line
#### Sending Market Ids
Most of exchanges' API endpoints will require an exchange-specific market symbol or trading pair or instrument to be specified in the request.
**We don't send unified symbols to exchanges directly!** They are not interchangeable! There is a significant difference between *exchange-specific market-ids* and *unified symbols*! This is explained in the Manual, here:
- https://github.com/ccxt/ccxt/wiki/Manual#markets
- https://github.com/ccxt/ccxt/wiki/Manual#symbols-and-market-ids
**NEVER DO THIS:**
```JavaScript
async fetchTicker (symbol, params = {}) {
const request = {
'pair': symbol, // very bad, sending unified symbols to the exchange directly
};
const response = await this.publicGetEndpoint (request);
// parse in a unified way...
}
```
**DO NOT DO THIS EITHER:**
```JavaScript
async fetchTicker (symbol, params = {}) {
const request = {
'symbol': symbol, // very bad, sending unified symbols to the exchange directly
};
const response = await this.publicGetEndpoint (request);
// parse in a unified way...
}
```
Instead of sending a unified CCXT symbol to the exchange, we **always** take the exchange-specific market-`id` that corresponds to that symbol. If it so happens that an exchange specific market-id is exactly the same as the CCXT unified symbol – that's a happy coincidence, but we never rely on that in the unified CCXT API.
To get the exchange-specific market-id by a unified CCXT symbol, use the following methods:
- `this.market (symbol)` – returns the entire unified market structure, containing the `id`, `baseId`, `quoteId`, and many other interesting things
- `this.marketId (symbol)` – returns just the exchange-specific `id` of a market by a unified symbol (if you don't need anything else)
**GOOD EXAMPLES:**
```JavaScript
async fetchTicker (symbol, params = {}) {
const market = this.market (symbol); // the entire market structure
const request = {
'pair': market['id'], // good, they may me equal, but often differ, it's ok
};
const response = await this.publicGetEndpoint (this.extend (request, params));
// parse in a unified way...
}
```
```JavaScript
async fetchTicker (symbol, params = {}) {
const marketId = this.marketId (symbol); // just the id
const request = {
'symbol': marketId, // good, they may me equal, but often differ, it's ok
};
const response = await this.publicGetEndpoint (this.extend (request, params));
// parse in a unified way...
}
```
#### Parsing Symbols
When sending requests to the exchange unified symbols have to be _"converted"_ to exchange-specific market-`id`s like shown above. The same is true on the other side – when receiving an exchange response it has an exchange-specific market-`id` inside it that has to be _"converted back"_ to a unified CCXT symbol.
**We don't put exchange-specific market-`id`s in unified structures directly!** We can't freely interchange symbols with ids! There is a significant difference between an *exchange-specific market-ids* and *unified symbols*! This is explained in the Manual, here:
- https://github.com/ccxt/ccxt/wiki/Manual#markets
- https://github.com/ccxt/ccxt/wiki/Manual#symbols-and-market-ids
**NEVER DO THIS:**:
```JavaScript
parseTrade (trade, market = undefined) {
// parsing code...
return {
'info': trade,
'symbol': trade['pair'], // very bad, returning exchange-specific market-ids in a unified structure!
// other fields...
};
}
```
**DO NOT DO THIS EITHER**
```JavaScript
parseTrade (trade, market = undefined) {
// parsing code...
return {
'info': trade,
'symbol': trade['symbol'], // very bad, returning exchange-specific market-ids in a unified structure!
// other fields...
};
}
```
In order to handle the market-`id` properly it has to be looked-up in the info cached on this exchange with `loadMarkets()`:
**GOOD EXAMPLE:**
```JavaScript
parseTrade (trade, market = undefined) {
const marketId = this.safeString (trade, 'pair');
// safeSymbol is used to parse the market id to a unified symbol
const symbol = this.safeSymbol (marketId, market);
return {
'info': trade,
'symbol': symbol, // very good, a unified symbol here now
// other fields...
};
}
```
#### Accessing Dictionary Keys
In JavaScript, dictionary keys can be accessed in two notations:
- `object['key']` (single-quoted string key notation)
- `object.key` (property notation)
Both work almost identically, and one is implicitly converted to another upon executing the JavaScript code.
While the above does work in JavaScript, it will not work in Python or PHP. In most languages, associative dictionary keys are not treated in the same way as properties. Therefore, in Python `object.key` is not the same as `object['key']`. In PHP `$object->key` is not the same as `$object['key']` as well. Languages that differentiate between associative keys and properties use different notations for the two.
To keep the code transpileable, please, remember this simple rule: *always use the single-quoted string key notation `object['key']` for accessing all associative dictionary keys in all languages everywhere throughout this library!*
#### Sanitizing Input With `safe`-Methods
JavaScript is less restrictive than other languages. It will tolerate an attempt to dereference a non-existent key where other languages will throw an Exception:
```JavaScript
// JavaScript
const someObject = {}
if (someObject['nonExistentKey']) {
// the body of this conditional will not execute in JavaScript
// because someObject['nonExistentKey'] === undefined === false
// but JavaScript will not throw an exception on the if-clause
}
```
However, the above logic with *"an undefined value by default"* will not work in Python or PHP.
```Python
# Python
some_dictionary = {}
# breaks
if some_dictionary['nonExistentKey']:
# in Python the attempt to dereference the nonExistentKey value
# will throw a standard built-in KeyError exception
# works
if 'nonExistentKey' in some_dictionary and some_dictionary['nonExistentKey']:
# this is a way to check if the key exists before accessing the value
# also works
if some_dictionary.get('nonExistentKey'):
# another a way to check if the key exists before accessing the value...
```
Most languages will not tolerate an attempt to access a non-existent key in an object.
For the above reasons, please, **never do this** in the transpiled JS files:
```JavaScript
// JavaScript
const value = object['key'] || other_value; // will not work in Python or PHP!
if (object['key'] || other_value) { /* will not work in Python or PHP! */ }
```
Therefore we have a family of `safe*` functions:
- `safeInteger (object, key)`, `safeInteger2 (object, key1, key2)`
- `safeFloat (object, key)`, `safeFloat2 (object, key1, key2)`
- `safeString (object, key)`, `safeString2 (object, key1, key2)`
- `safeValue (object, key)`, `safeValue2 (object, key1, key2)`
The `safeValue` function is used for objects inside objects, arrays inside objects and boolean `true/false` values.
The above safe-functions will check for the existence of the key in the object and will properly return `undefined/None/null` values for JS/Python/PHP. Each function also accepts the default value to be returned instead of `undefined/None/null` in the last argument.
Alternatively, you could check for the key existence first...
So, you have to change this:
```JavaScript
if (params['foo'] !== undefined) {
}
```
↓
```JavaScript
const foo = this.safeValue (params, 'foo');
if (foo !== undefined) {
}
```
Or:
```JavaScript
if ('foo' in params) {
}
```
#### Using Base Class Cryptography Methods For Authentication
Do not reinvent the wheel. Always use base-class methods for cryptography.
The CCXT library supports the following authentication algorithms and cryptography algorithms:
- HMAC
- JWT (JSON Web Token)
- RSA
- ECDSA Elliptic Curve Cryptography
- NIST P256
- secp256k1
- OTP 2FA (one-time password 2-factor authentication)
The base `Exchange` class offers several methods that are key to practically all cryptography in this lib. Derived exchange implementations must not use external dependencies for cryptography, everything should be done with base methods only.
- `hash (message, hash = 'md5', digest = 'hex')`
- `hmac (message, secret, hash = 'sha256', digest = 'hex')`
- `jwt (message, secret, hash = 'HS256')`
- `rsa (message, secret, alg = 'RS256')`
- `ecdsa (request, secret, algorithm = 'p256', hash = undefined)`
- `totp (secret)`
- `stringToBase64()`, `base64ToBinary()`, `binaryToBase64()`...
The `hash()` method supports the following `hash` algorithms:
- `'md5'`
- `'sha1'`
- `'sha3'`
- `'sha256'`
- `'sha384'`
- `'sha512'`
- `'keccak'`
The `digest` encoding argument accepts the following values:
- `'hex'`
- `'binary'`
The `hmac()` method also supports `'base64'` for the `digest` argument. This is for `hmac()` only, other implementations should use `'binary'` with `binaryToBase64()`.
#### Timestamps
**All timestamps throughout all unified structures within this library are integer timestamp _in milliseconds_!**
In order to convert to milliseconds timestamps, CCXT implements the following methods:
```JavaScript
const data = {
'unixTimestampInSeconds': 1565242530,
'unixTimestampInMilliseconds': 1565242530165,
'stringInSeconds': '1565242530',
};
// convert to integer if the underlying value is already in milliseconds
const timestamp = this.safeInteger (data, 'unixTimestampInMilliseconds'); // === 1565242530165
// convert to integer and multiply by a thousand if the value is a UNIX timestamp in seconds
const timestamp = this.safeTimestamp (data, 'unixTimestampInSeconds'); // === 1565242530000
// convert to integer and multiply by a thousand if the value is in seconds
const timestamp = this.safeTimestamp (data, 'stringInSeconds'); // === 1565242530000
```
#### Working With Array Lengths
In JavaScript the common syntax to get a length of a string or an array is to reference the `.length` property like shown here:
```JavaScript
someArray.length
// or
someString.length
```
And it works for both strings and arrays. In Python this is done in a similar way:
```Python
len(some_array)
# or
len(some_string)
```
So the length is accessible in the same way for both strings and arrays and both work fine.
However, with PHP this is different, so the syntax for string lengths and array lengths is different:
```PHP
count(some_array);
// or
strlen(some_string); // or mb_strlen
```
Because the transpiler works line-by-line and does no code introspection, it cannot tell arrays from strings and cannot properly transpile `.length` to PHP without additional hinting. It will always transpile JS `.length` to PHP `strlen` and will prefer string lengths over array lengths. In order to indicate an array length properly we have to do the following:
```JavaScript
const arrayLength = someArray.length;
// the above line ends with .length;
// that ending is a hint for the transpiler that will recognize someArray
// as an array variable in this place, rather than a string type variable
// now we can use arrayLength for the arithmetic
```
That `.length;` line ending does the trick. The only case when the array `.length` is preferred over the string `.length` is the `for` loop. In the header of the `for` loop, the `.length` always refers to array length (not string length).
#### Adding Numbers And Concatenating Strings
In JS the arithmetic addition `+` operator handles both strings and numbers. So, it can concatenate strings with `+` and can sum up numbers with `+` as well. The same is true with Python. With PHP this is different, so it has different operators for string concatenation (the "dot" operator `.`) and for arithmetic addition (the "plus" operator `+`). Once again, because the transpiler does no code introspection it cannot tell if you're adding up numbers or strings in JS. This works fine until you want to transpile this to other languages, be it PHP or whatever other language it is. In order to help the transpiler we have to use `this.sum` for arithmetic additions.
The rule of thumb is: **`+` is for string concatenation only (!)** and **`this.sum (a, b, c, ...)` is for arithmetic additions**. The same logic applies to operator `+=` vs operator `.=` – `this.sum()` has to be used in those cases as well.
#### Formatting Decimal Numbers To Precision
The `.toFixed ()` method has [known rounding bugs](https://www.google.com/search?q=toFixed+bug) in JavaScript, and so do the other rounding methods in the other languages as well. In order to work with number formatting consistently, we need to use the [`decimalToPrecision` method as described in the Manual](https://github.com/ccxt/ccxt/wiki/Manual#methods-for-formatting-decimals).
#### Escaped Control Characters
When using strings containing control characters like `"\n"`, `"\t"`, always enclose them in double quotes (`"`), not single quotes (`'`)! Single-quoted strings are not parsed for control characters and are treated as is in many languages apart from JavaScript. Therefore for tabs and newlines to work in PHP, we need to surround them with double quotes (especially in the `sign()` implementation).
Bad:
```JavaScript
const a = 'GET' + method.toLowerCase () + '\n' + path;
const b = 'api\nfoobar.com\n';
```
Good:
```JavaScript
const a = 'GET' + method.toLowerCase () + "\n" + path; // eslint-disable-line quotes
// eslint-disable-next-line quotes
const b = "api\nfoobar.com\n";
```
**↑ The `eslint-*` comments are mandatory!**
#### Using Ternary Conditionals
Do not use heavy ternary (`?:`) conditionals, **always use brackets in ternary operators!**
Despite that there is operator precedence in the programming languages themselves, the transpiler is regex-based and it does no code introspection, therefore it treats everything as plaintext.
The brackets are needed to hint the transpiler which part of the conditional is which. In the absence of brackets it's hard to understand the line and the intent of the developer.
Here are some examples of a badly-designed code that will break the transpiler:
```JavaScript
// this is an example of bad codestyle that will likely break the transpiler
const foo = {
'bar': 'a' + qux === 'baz' ? this.a () : this.b () + 'b',
};
```
```JavaScript
// this confuses the transpiler and a human developer as well
const foo = 'bar' + baz + qux ? 'a' : '' + this.c ();
```
Adding surrounding brackets to corresponding parts would be a more or less correct way to resolve it.
```JavaScript
const foo = {
'bar': (qux === 'baz') ? this.a () : this.b (), // much better now
};
```
The universally-working way to solve it is to just break the complex line into a few simpler lines, even at a cost of adding extra lines and conditionals:
```JavaScript
// before:
// const foo = {
// 'bar': 'a' + qux === 'baz' ? this.a () : this.b () + 'b',
// };
// ----------------------------------------------------------------------------
// after:
const bar = (qux === 'baz') ? this.a () : this.b ();
const foo = {
'bar': 'a' + bar + 'b',
};
```
Or even:
```JavaScript
// before:
// const foo = 'bar' + baz + qux ? 'a' : '' + this.c ();
// ----------------------------------------------------------------------------
// after:
let foo = 'bar' + baz;
if (qux) {
foo += 'a';
};
foo += this.c ();
```
---
### New Exchange Integrations
**REMEMBER:** The key reason why this library is used at all is **Unification**. When developing a new exchange file the goal is not to implement it somehow, but to implement it in a very pedantic, precise and exact way, just as the other exchanges are implemented. For that we have to copy bits of logic from other exchanges and make sure that the new exchange conforms to the Manual in the following aspects:
- market ids, trading pair symbols, currency ids, token codes, symbolic unification and `commonCurrencies` must be standardized in all parsing methods (`fetchMarkets`, `fetchCurrencies`, `parseTrade`, `parseOrder`, ...)
- all unified API method names and arguments are standard – can't add or change them freely
- all parser input must be `safe`-sanitized as [described above](#sanitizing-input-with-safe-methods)
- for bulk operations the base methods should be used (`parseTrades`, `parseOrders`, note the `s` plural ending)
- use as much of base functionality as you can, do not reinvent the wheel, nor the bicycle, nor the bicycle wheel
- respect default argument values in `fetch`-methods, check if `since` and `limit` are `undefined` and do not send them to the exchange, we intentionally use the exchanges' defaults in such cases
- when implementing a unified method that has some arguments – we can't ignore or miss any of those arguments
- all structures returned from the unified methods must conform to their specifications from the Manual
- all API endpoints have to be listed out with proper support for params substituted in the URLs
Please, see the following document for new integrations: https://github.com/ccxt/ccxt/wiki/Requirements
A quick merge of a Pull Request for a new exchange integration depends on consistency and compliance with the above unified rules and standards. Breaking one of those is the key reason for not merging a Pull Request.
**If you want to add (support for) another exchange, or implement a new method for a particular exchange, then the best way to make it a consistent improvement is to learn from example. Take a look at how same things are implemented in other exchanges (we recommend certified exchanges) and try to copy the code flow and style.**
The basic JSON-skeleton for a new exchange integration is as follows:
```
{
'id': 'example',
'name': 'Example Exchange',
'country': [ 'US', 'EU', 'CN', 'RU' ],
'rateLimit': 1000,
'version': '1',
'comment': 'This comment is optional',
'urls': {
'logo': 'https://example.com/image.jpg',
'api': 'https://api.example.com/api',
'www': 'https://www.example.com',
'doc': [
'https://www.example.com/docs/api',
'https://www.example.com/docs/howto',
'https://github.com/example/docs',
],
},
'api': {
'public': {
'get': [
'endpoint/example',
'orderbook/{pair}/full',
'{pair}/ticker',
],
},
'private': {
'post': [
'balance',
],
},
},
}
```
### Implicit API Methods
In the code for each exchange, you'll notice that the functions that make API requests aren't explicitly defined. This is because the `api` definition in the exchange description JSON is used to create *magic functions* (aka *partial functions* or *closures*) inside the exchange subclass. That implicit injection is done by the `defineRestApi/define_rest_api` base exchange method.
Each partial function takes a dictionary of `params` and returns the API response. In the example JSON above, the `'endpoint/example'` results in the injection of a `this.publicGetEndpointExample` function. Similarly, the `'orderbook/{pair}/full'` results in a `this.publicGetOrderbookPairFull` function, that takes a ``pair`` parameter (again, passed in the `params` argument).
Upon instantiation the base exchange class takes each URL from its list of endpoints, splits it into words, and then makes up a callable function name from those words by using a partial construct. That process is the same in JS, Python and PHP as well. It is also described here:
- https://github.com/ccxt/ccxt/wiki/Manual#api-methods--endpoints
- https://github.com/ccxt/ccxt/wiki/Manual#implicit-api-methods
- https://github.com/ccxt-dev/ccxt/wiki/Manual#api-method-naming-conventions
```UNDER CONSTRUCTION```
### Continuous Integration
Builds are automated with [Travis CI](https://travis-ci.org/ccxt/ccxt). The build steps for Travis CI are described in the [`.travis.yml`](https://github.com/ccxt/ccxt/blob/master/.travis.yml) file.
Windows builds are automated with [Appveyor](https://ci.appveyor.com/project/ccxt/ccxt). The build steps for Appveyor are in the [`appveyor.yml`](https://github.com/ccxt/ccxt/blob/master/appveyor.yml) file.
Incoming pull requests are automatically validated by the CI service. You can watch the build process online here: [travis-ci.org/ccxt/ccxt/builds](https://travis-ci.org/ccxt/ccxt/builds).
### How To Build & Run Tests On Your Local Machine
Before building for the first time, install Node dependencies (skip this step if you're running our Docker image):
```
npm install
```
The command below will build everything and generate PHP/Python versions from source JS files:
```
npm run build
```
The following command will test the built generated files (for all exchanges, symbols and languages):
```
node run-tests
```
You can restrict tests to a specific language, a particular exchange or symbol:
```
node run-tests [--php] [--js] [--python] [--python3] [exchange] [symbol]
```
For example, the first of the following lines will only test the source JS version of the library (`ccxt.js`). It does not require an `npm run build` before running it (can be useful if you need to verify quickly whether your changes break the code or not):
```shell
node run-tests --js # test master ccxt.js, all exchanges
# other examples require the 'npm run build' to run
node run-tests --python # test Python 2 version, all exchanges
node run-tests --php bitfinex # test Bitfinex with PHP
node run-tests --python3 kraken # test Kraken with Python 3, requires 'npm run build'
```
## Committing Changes To The Repository
The build process generates many changes in the transpiled exchange files, e.g. for Python and PHP. **You should NOT commit them to GitHub, commit only the base (JS) file changes please**.
You can hide the changes in the generated files by running this command (after that, the generated files are no longer marked as changed):
```
npm run git-ignore-generated-files
```
Previously we had that command implemented as a final build step, but it caused problems with subsequent `git pull` and also branch selection commands (when a conflict occurred in those files that have been marked as ignored). So if you experience an issue with that, you can un-ignore those files by executing:
```
npm run git-unignore-generated-files
```
## Financial Contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/ccxt).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
```
ETH 0x26a3CB49578F07000575405a57888681249c35Fd (ETH only!)
BTC 33RmVRfhK2WZVQR1R83h2e9yXoqRNDvJva
BCH 1GN9p233TvNcNQFthCgfiHUnj5JRKEc2Ze
LTC LbT8mkAqQBphc4yxLXEDgYDfEax74et3bP
```
## Credits
### Contributors
Thank you to all the people who have already contributed to ccxt!
<a href="graphs/contributors"><img src="https://opencollective.com/ccxt/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/ccxt#backer)]
<a href="https://opencollective.com/ccxt#backers" target="_blank"><img src="https://opencollective.com/ccxt/backers.svg?width=890"></a>
### Supporters
Support this project by becoming a supporter. Your avatar will show up here with a link to your website.
[[Become a supporter](https://opencollective.com/ccxt#supporter)]
<a href="https://opencollective.com/ccxt/tiers/supporter/0/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/0/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/1/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/1/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/2/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/2/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/3/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/3/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/4/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/4/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/5/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/5/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/6/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/6/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/7/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/7/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/8/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/8/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/9/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/9/avatar.svg"></a>
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/ccxt#sponsor))
[[Become a sponsor](https://opencollective.com/ccxt#sponsor)]
<a href="https://opencollective.com/ccxt/tiers/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/9/avatar.svg"></a>
@@ -0,0 +1,21 @@
MIT License
Copyright © 2017 Igor Kroitor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,551 @@
# CCXT – CryptoCurrency eXchange Trading Library
[![Build Status](https://travis-ci.com/ccxt/ccxt.svg?branch=master)](https://travis-ci.com/ccxt/ccxt) [![npm](https://img.shields.io/npm/v/ccxt.svg)](https://npmjs.com/package/ccxt) [![PyPI](https://img.shields.io/pypi/v/ccxt.svg)](https://pypi.python.org/pypi/ccxt) [![NPM Downloads](https://img.shields.io/npm/dm/ccxt.svg)](https://www.npmjs.com/package/ccxt) [![Discord](https://img.shields.io/discord/690203284119617602?logo=discord&logoColor=white)](https://discord.gg/dhzSKYU) [![Supported Exchanges](https://img.shields.io/badge/exchanges-123-blue.svg)](https://github.com/ccxt/ccxt/wiki/Exchange-Markets) [![Open Collective](https://opencollective.com/ccxt/backers/badge.svg)](https://opencollective.com/ccxt)
[![Twitter Follow](https://img.shields.io/twitter/follow/ccxt_official.svg?style=social&label=CCXT)](https://twitter.com/ccxt_official)
A JavaScript / Python / PHP library for cryptocurrency trading and e-commerce with support for many bitcoin/ether/altcoin exchange markets and merchant APIs.
### [Install](#install) · [Usage](#usage) · [Manual](https://github.com/ccxt/ccxt/wiki) · [FAQ](https://github.com/ccxt/ccxt/wiki/FAQ) · [Examples](https://github.com/ccxt/ccxt/tree/master/examples) · [Contributing](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md) · [Social](#social)
The **CCXT** library is used to connect and trade with cryptocurrency exchanges and payment processing services worldwide. It provides quick access to market data for storage, analysis, visualization, indicator development, algorithmic trading, strategy backtesting, bot programming, and related software engineering.
It is intended to be used by **coders, developers, technically-skilled traders, data-scientists and financial analysts** for building trading algorithms.
Current feature list:
- support for many cryptocurrency exchanges — more coming soon
- fully implemented public and private APIs
- optional normalized data for cross-exchange analytics and arbitrage
- an out of the box unified API that is extremely easy to integrate
- works in Node 7.6+, Python 3, PHP 5.4+, and web browsers
## Sponsored Promotion
[![CCXT Pro – A JavaScript / Python / PHP cryptocurrency exchange trading WebSocket API for professionals](https://user-images.githubusercontent.com/1294454/93346896-22dfb080-f83d-11ea-957f-6b8ae008f62a.png)](https://ccxt.pro) **[A JavaScript / Python / PHP cryptocurrency exchange trading WebSocket API for professionals](https://ccxt.pro)**
## See Also
- <sub>[![Quadency](https://user-images.githubusercontent.com/1294454/69334382-a0618100-0c6b-11ea-9ba9-40aa97440cfa.png)](https://quadency.com?utm_source=ccxt)</sub>&nbsp; **[Quadency](https://quadency.com?utm_source=ccxt)**&nbsp;&mdash;&nbsp;trading automation terminal for leading exchanges with streaming APIs + Python IDE.
- <sub>[![TabTrader](https://user-images.githubusercontent.com/1294454/66755907-9c3e8880-eea1-11e9-846e-0bff349ceb87.png)](https://tab-trader.com/?utm_source=ccxt)</sub>&nbsp; **[TabTrader](https://tab-trader.com/?utm_source=ccxt)**&nbsp;&mdash;&nbsp;trading on all exchanges in one app. Avaliable on [Android](https://play.google.com/store/apps/details?id=com.tabtrader.android&referrer=utm_source%3Dccxt) and [iOS](https://itunes.apple.com/app/apple-store/id1095716562?mt=8).
## Certified Cryptocurrency Exchanges
| logo | id | name | ver | doc | certified | pro |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------|:---:|:----------------------------------------------------------------------------------:|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| [![binance](https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg)](https://www.binance.com/?ref=10205187) | binance | [Binance](https://www.binance.com/?ref=10205187) | * | [API](https://binance-docs.github.io/apidocs/spot/en) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitfinex](https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg)](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 1 | [API](https://docs.bitfinex.com/v1/docs) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bittrex](https://user-images.githubusercontent.com/51840849/87153921-edf53180-c2c0-11ea-96b9-f2a9a95a455b.jpg)](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | bittrex | [Bittrex](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | 3 | [API](https://bittrex.github.io/api/v3) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitvavo](https://user-images.githubusercontent.com/1294454/83165440-2f1cf200-a116-11ea-9046-a255d09fb2ed.jpg)](https://bitvavo.com/?a=24F34952F7) | bitvavo | [Bitvavo](https://bitvavo.com/?a=24F34952F7) | 2 | [API](https://docs.bitvavo.com/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bytetrade](https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg)](https://www.byte-trade.com) | bytetrade | [ByteTrade](https://www.byte-trade.com) | * | [API](https://docs.byte-trade.com/#description) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
| [![currencycom](https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg)](https://currency.com/trading/signup?c=362jaimv&pid=referral) | currencycom | [Currency.com](https://currency.com/trading/signup?c=362jaimv&pid=referral) | 1 | [API](https://currency.com/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![eterbase](https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg)](https://eterbase.exchange/invite/1wjjh4Pe) | eterbase | [Eterbase](https://eterbase.exchange/invite/1wjjh4Pe) | 1 | [API](https://developers.eterbase.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
| [![ftx](https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg)](https://ftx.com/#a=1623029) | ftx | [FTX](https://ftx.com/#a=1623029) | * | [API](https://github.com/ftexchange/ftx) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![idex](https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg)](https://idex.io) | idex | [IDEX](https://idex.io) | 2 | [API](https://docs.idex.io/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![kraken](https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg)](https://www.kraken.com) | kraken | [Kraken](https://www.kraken.com) | 0 | [API](https://www.kraken.com/features/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![upbit](https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg)](https://upbit.com) | upbit | [Upbit](https://upbit.com) | 1 | [API](https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![wavesexchange](https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg)](https://waves.exchange) | wavesexchange | [Waves.Exchange](https://waves.exchange) | * | [API](https://docs.waves.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
| [![xena](https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg)](https://xena.exchange) | xena | [Xena Exchange](https://xena.exchange) | * | [API](https://support.xena.exchange/support/solutions/44000808700) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
## Supported Cryptocurrency Exchange Markets
The CCXT library currently supports the following 123 cryptocurrency exchange markets and trading APIs:
| logo | id | name | ver | doc | certified | pro |
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|-----------------------------------------------------------------------------------------|:---:|:-------------------------------------------------------------------------------------------:|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| [![acx](https://user-images.githubusercontent.com/1294454/30247614-1fe61c74-9621-11e7-9e8c-f1a627afa279.jpg)](https://acx.io) | acx | [ACX](https://acx.io) | 2 | [API](https://acx.io/documents/api_v2) | | |
| [![aofex](https://user-images.githubusercontent.com/51840849/77670271-056d1080-6f97-11ea-9ac2-4268e9ed0c1f.jpg)](https://aofex.com/#/register?key=9763840) | aofex | [AOFEX](https://aofex.com/#/register?key=9763840) | * | [API](https://aofex.zendesk.com/hc/en-us/sections/360005576574-API) | | |
| [![bcex](https://user-images.githubusercontent.com/51840849/77231516-851c6900-6bac-11ea-8fd6-ee5c23eddbd4.jpg)](https://www.bcex.top/register?invite_code=758978&lang=en) | bcex | [BCEX](https://www.bcex.top/register?invite_code=758978&lang=en) | 1 | [API](https://github.com/BCEX-TECHNOLOGY-LIMITED/API_Docs/wiki/Interface) | | |
| [![bequant](https://user-images.githubusercontent.com/1294454/55248342-a75dfe00-525a-11e9-8aa2-05e9dca943c6.jpg)](https://bequant.io) | bequant | [Bequant](https://bequant.io) | 2 | [API](https://api.bequant.io/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bibox](https://user-images.githubusercontent.com/51840849/77257418-3262b000-6c85-11ea-8fb8-20bdf20b3592.jpg)](https://w2.bibox365.com/login/register?invite_code=05Kj3I) | bibox | [Bibox](https://w2.bibox365.com/login/register?invite_code=05Kj3I) | 1 | [API](https://biboxcom.github.io/en/) | | |
| [![bigone](https://user-images.githubusercontent.com/1294454/69354403-1d532180-0c91-11ea-88ed-44c06cefdf87.jpg)](https://b1.run/users/new?code=D3LLBVFT) | bigone | [BigONE](https://b1.run/users/new?code=D3LLBVFT) | 3 | [API](https://open.big.one/docs/api.html) | | |
| [![binance](https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg)](https://www.binance.com/?ref=10205187) | binance | [Binance](https://www.binance.com/?ref=10205187) | * | [API](https://binance-docs.github.io/apidocs/spot/en) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![binanceus](https://user-images.githubusercontent.com/1294454/65177307-217b7c80-da5f-11e9-876e-0b748ba0a358.jpg)](https://www.binance.us/?ref=35005074) | binanceus | [Binance US](https://www.binance.us/?ref=35005074) | * | [API](https://github.com/binance-us/binance-official-api-docs) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bit2c](https://user-images.githubusercontent.com/1294454/27766119-3593220e-5ece-11e7-8b3a-5a041f6bcc3f.jpg)](https://bit2c.co.il/Aff/63bfed10-e359-420c-ab5a-ad368dab0baf) | bit2c | [Bit2C](https://bit2c.co.il/Aff/63bfed10-e359-420c-ab5a-ad368dab0baf) | * | [API](https://www.bit2c.co.il/home/api) | | |
| [![bitbank](https://user-images.githubusercontent.com/1294454/37808081-b87f2d9c-2e59-11e8-894d-c1900b7584fe.jpg)](https://bitbank.cc/) | bitbank | [bitbank](https://bitbank.cc/) | 1 | [API](https://docs.bitbank.cc/) | | |
| [![bitbay](https://user-images.githubusercontent.com/1294454/27766132-978a7bd8-5ece-11e7-9540-bc96d1e9bbb8.jpg)](https://auth.bitbay.net/ref/jHlbB4mIkdS1) | bitbay | [BitBay](https://auth.bitbay.net/ref/jHlbB4mIkdS1) | * | [API](https://bitbay.net/public-api) | | |
| [![bitcoincom](https://user-images.githubusercontent.com/1294454/97296144-514fa300-1861-11eb-952b-3d55d492200b.jpg)](https://exchange.bitcoin.com/referral/da948b21d6c92d69) | bitcoincom | [bitcoin.com](https://exchange.bitcoin.com/referral/da948b21d6c92d69) | 2 | [API](https://api.exchange.bitcoin.com/api/2/explore) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitfinex](https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg)](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 1 | [API](https://docs.bitfinex.com/v1/docs) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitfinex2](https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg)](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex2 | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 2 | [API](https://docs.bitfinex.com/v2/docs/) | | |
| [![bitflyer](https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg)](https://bitflyer.com) | bitflyer | [bitFlyer](https://bitflyer.com) | 1 | [API](https://lightning.bitflyer.com/docs?lang=en) | | |
| [![bitforex](https://user-images.githubusercontent.com/51840849/87295553-1160ec00-c50e-11ea-8ea0-df79276a9646.jpg)](https://www.bitforex.com/en/invitationRegister?inviterId=1867438) | bitforex | [Bitforex](https://www.bitforex.com/en/invitationRegister?inviterId=1867438) | 1 | [API](https://github.com/githubdev2020/API_Doc_en/wiki) | | |
| [![bitget](https://user-images.githubusercontent.com/51840849/88317935-a8a21c80-cd22-11ea-8e2b-4b9fac5975eb.jpg)](https://www.bitget.com/expressly?languageType=0&channelCode=ccxt&vipCode=tg9j) | bitget | [Bitget](https://www.bitget.com/expressly?languageType=0&channelCode=ccxt&vipCode=tg9j) | 3 | [API](https://bitgetlimited.github.io/apidoc/en/swap) | | |
| [![bithumb](https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg)](https://www.bithumb.com) | bithumb | [Bithumb](https://www.bithumb.com) | * | [API](https://apidocs.bithumb.com) | | |
| [![bitkk](https://user-images.githubusercontent.com/1294454/32859187-cd5214f0-ca5e-11e7-967d-96568e2e2bd1.jpg)](https://www.bitkk.com) | bitkk | [bitkk](https://www.bitkk.com) | 1 | [API](https://www.bitkk.com/i/developer) | | |
| [![bitmart](https://user-images.githubusercontent.com/1294454/61835713-a2662f80-ae85-11e9-9d00-6442919701fd.jpg)](http://www.bitmart.com/?r=rQCFLh) | bitmart | [BitMart](http://www.bitmart.com/?r=rQCFLh) | 1 | [API](https://github.com/bitmartexchange/bitmart-official-api-docs) | | |
| [![bitmax](https://user-images.githubusercontent.com/1294454/66820319-19710880-ef49-11e9-8fbe-16be62a11992.jpg)](https://bitmax.io/#/register?inviteCode=EL6BXBQM) | bitmax | [BitMax](https://bitmax.io/#/register?inviteCode=EL6BXBQM) | 1 | [API](https://bitmax-exchange.github.io/bitmax-pro-api/#bitmax-pro-api-documentation) | | |
| [![bitmex](https://user-images.githubusercontent.com/1294454/27766319-f653c6e6-5ed4-11e7-933d-f0bc3699ae8f.jpg)](https://www.bitmex.com/register/upZpOX) | bitmex | [BitMEX](https://www.bitmex.com/register/upZpOX) | 1 | [API](https://www.bitmex.com/app/apiOverview) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitpanda](https://user-images.githubusercontent.com/51840849/87591171-9a377d80-c6f0-11ea-94ac-97a126eac3bc.jpg)](https://www.bitpanda.com/en/pro) | bitpanda | [Bitpanda Pro](https://www.bitpanda.com/en/pro) | 1 | [API](https://developers.bitpanda.com/exchange/) | | |
| [![bitso](https://user-images.githubusercontent.com/51840849/87295554-11f98280-c50e-11ea-80d6-15b3bafa8cbf.jpg)](https://bitso.com/?ref=itej) | bitso | [Bitso](https://bitso.com/?ref=itej) | 3 | [API](https://bitso.com/api_info) | | |
| [![bitstamp](https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg)](https://www.bitstamp.net) | bitstamp | [Bitstamp](https://www.bitstamp.net) | 2 | [API](https://www.bitstamp.net/api) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitstamp1](https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg)](https://www.bitstamp.net) | bitstamp1 | [Bitstamp](https://www.bitstamp.net) | 1 | [API](https://www.bitstamp.net/api) | | |
| [![bittrex](https://user-images.githubusercontent.com/51840849/87153921-edf53180-c2c0-11ea-96b9-f2a9a95a455b.jpg)](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | bittrex | [Bittrex](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | 3 | [API](https://bittrex.github.io/api/v3) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitvavo](https://user-images.githubusercontent.com/1294454/83165440-2f1cf200-a116-11ea-9046-a255d09fb2ed.jpg)](https://bitvavo.com/?a=24F34952F7) | bitvavo | [Bitvavo](https://bitvavo.com/?a=24F34952F7) | 2 | [API](https://docs.bitvavo.com/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![bitz](https://user-images.githubusercontent.com/51840849/87443304-fec5e000-c5fd-11ea-98f8-ba8e67f7eaff.jpg)](https://u.bitz.com/register?invite_code=1429193) | bitz | [Bit-Z](https://u.bitz.com/register?invite_code=1429193) | 2 | [API](https://apidoc.bitz.com/en/) | | |
| [![bl3p](https://user-images.githubusercontent.com/1294454/28501752-60c21b82-6feb-11e7-818b-055ee6d0e754.jpg)](https://bl3p.eu) | bl3p | [BL3P](https://bl3p.eu) | 1 | [API](https://github.com/BitonicNL/bl3p-api/tree/master/docs) | | |
| [![bleutrade](https://user-images.githubusercontent.com/1294454/30303000-b602dbe6-976d-11e7-956d-36c5049c01e7.jpg)](https://bleutrade.com) | bleutrade | [Bleutrade](https://bleutrade.com) | * | [API](https://app.swaggerhub.com/apis-docs/bleu/white-label/3.0.0) | | |
| [![braziliex](https://user-images.githubusercontent.com/1294454/34703593-c4498674-f504-11e7-8d14-ff8e44fb78c1.jpg)](https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465) | braziliex | [Braziliex](https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465) | * | [API](https://braziliex.com/exchange/api.php) | | |
| [![btcalpha](https://user-images.githubusercontent.com/1294454/42625213-dabaa5da-85cf-11e8-8f99-aa8f8f7699f0.jpg)](https://btc-alpha.com/?r=123788) | btcalpha | [BTC-Alpha](https://btc-alpha.com/?r=123788) | 1 | [API](https://btc-alpha.github.io/api-docs) | | |
| [![btcbox](https://user-images.githubusercontent.com/51840849/87327317-98c55400-c53c-11ea-9a11-81f7d951cc74.jpg)](https://www.btcbox.co.jp/) | btcbox | [BtcBox](https://www.btcbox.co.jp/) | 1 | [API](https://blog.btcbox.jp/en/archives/8762) | | |
| [![btcmarkets](https://user-images.githubusercontent.com/51840849/89731817-b3fb8480-da52-11ea-817f-783b08aaf32b.jpg)](https://btcmarkets.net) | btcmarkets | [BTC Markets](https://btcmarkets.net) | 3 | [API](https://api.btcmarkets.net/doc/v3) | | |
| [![btctradeua](https://user-images.githubusercontent.com/1294454/27941483-79fc7350-62d9-11e7-9f61-ac47f28fcd96.jpg)](https://btc-trade.com.ua/registration/22689) | btctradeua | [BTC Trade UA](https://btc-trade.com.ua/registration/22689) | * | [API](https://docs.google.com/document/d/1ocYA0yMy_RXd561sfG3qEPZ80kyll36HUxvCRe5GbhE/edit) | | |
| [![btcturk](https://user-images.githubusercontent.com/51840849/87153926-efbef500-c2c0-11ea-9842-05b63612c4b9.jpg)](https://www.btcturk.com) | btcturk | [BTCTurk](https://www.btcturk.com) | * | [API](https://github.com/BTCTrader/broker-api-docs) | | |
| [![buda](https://user-images.githubusercontent.com/1294454/47380619-8a029200-d706-11e8-91e0-8a391fe48de3.jpg)](https://www.buda.com) | buda | [Buda](https://www.buda.com) | 2 | [API](https://api.buda.com) | | |
| [![bw](https://user-images.githubusercontent.com/1294454/69436317-31128c80-0d52-11ea-91d1-eb7bb5818812.jpg)](https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0) | bw | [BW](https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0) | 1 | [API](https://github.com/bw-exchange/api_docs_en/wiki) | | |
| [![bybit](https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg)](https://www.bybit.com/app/register?ref=X7Prm) | bybit | [Bybit](https://www.bybit.com/app/register?ref=X7Prm) | 2 | [API](https://bybit-exchange.github.io/docs/inverse/) | | |
| [![bytetrade](https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg)](https://www.byte-trade.com) | bytetrade | [ByteTrade](https://www.byte-trade.com) | * | [API](https://docs.byte-trade.com/#description) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
| [![cex](https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg)](https://cex.io/r/0/up105393824/0/) | cex | [CEX.IO](https://cex.io/r/0/up105393824/0/) | * | [API](https://cex.io/cex-api) | | |
| [![chilebit](https://user-images.githubusercontent.com/1294454/27991414-1298f0d8-647f-11e7-9c40-d56409266336.jpg)](https://chilebit.net) | chilebit | [ChileBit](https://chilebit.net) | 1 | [API](https://blinktrade.com/docs) | | |
| [![coinbase](https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg)](https://www.coinbase.com/join/58cbe25a355148797479dbd2) | coinbase | [Coinbase](https://www.coinbase.com/join/58cbe25a355148797479dbd2) | 2 | [API](https://developers.coinbase.com/api/v2) | | |
| [![coinbaseprime](https://user-images.githubusercontent.com/1294454/44539184-29f26e00-a70c-11e8-868f-e907fc236a7c.jpg)](https://prime.coinbase.com) | coinbaseprime | [Coinbase Prime](https://prime.coinbase.com) | * | [API](https://docs.prime.coinbase.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![coinbasepro](https://user-images.githubusercontent.com/1294454/41764625-63b7ffde-760a-11e8-996d-a6328fa9347a.jpg)](https://pro.coinbase.com/) | coinbasepro | [Coinbase Pro](https://pro.coinbase.com/) | * | [API](https://docs.pro.coinbase.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![coincheck](https://user-images.githubusercontent.com/51840849/87182088-1d6d6380-c2ec-11ea-9c64-8ab9f9b289f5.jpg)](https://coincheck.com) | coincheck | [coincheck](https://coincheck.com) | * | [API](https://coincheck.com/documents/exchange/api) | | |
| [![coinegg](https://user-images.githubusercontent.com/1294454/36770310-adfa764e-1c5a-11e8-8e09-449daac3d2fb.jpg)](https://www.coinegg.com/user/register?invite=523218) | coinegg | [CoinEgg](https://www.coinegg.com/user/register?invite=523218) | * | [API](https://www.coinegg.com/explain.api.html) | | |
| [![coinex](https://user-images.githubusercontent.com/51840849/87182089-1e05fa00-c2ec-11ea-8da9-cc73b45abbbc.jpg)](https://www.coinex.com/register?refer_code=yw5fz) | coinex | [CoinEx](https://www.coinex.com/register?refer_code=yw5fz) | 1 | [API](https://github.com/coinexcom/coinex_exchange_api/wiki) | | |
| [![coinfalcon](https://user-images.githubusercontent.com/1294454/41822275-ed982188-77f5-11e8-92bb-496bcd14ca52.jpg)](https://coinfalcon.com/?ref=CFJSVGTUPASB) | coinfalcon | [CoinFalcon](https://coinfalcon.com/?ref=CFJSVGTUPASB) | 1 | [API](https://docs.coinfalcon.com) | | |
| [![coinfloor](https://user-images.githubusercontent.com/51840849/87153925-ef265e80-c2c0-11ea-91b5-020c804b90e0.jpg)](https://www.coinfloor.co.uk) | coinfloor | [coinfloor](https://www.coinfloor.co.uk) | * | [API](https://github.com/coinfloor/api) | | |
| [![coingi](https://user-images.githubusercontent.com/1294454/28619707-5c9232a8-7212-11e7-86d6-98fe5d15cc6e.jpg)](https://www.coingi.com/?r=XTPPMC) | coingi | [Coingi](https://www.coingi.com/?r=XTPPMC) | * | [API](https://coingi.docs.apiary.io) | | |
| [![coinmarketcap](https://user-images.githubusercontent.com/51840849/87182086-1cd4cd00-c2ec-11ea-9ec4-d0cf2a2abf62.jpg)](https://coinmarketcap.com) | coinmarketcap | [CoinMarketCap](https://coinmarketcap.com) | 1 | [API](https://coinmarketcap.com/api) | | |
| [![coinmate](https://user-images.githubusercontent.com/51840849/87460806-1c9f3f00-c616-11ea-8c46-a77018a8f3f4.jpg)](https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0) | coinmate | [CoinMate](https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0) | * | [API](https://coinmate.docs.apiary.io) | | |
| [![coinone](https://user-images.githubusercontent.com/1294454/38003300-adc12fba-323f-11e8-8525-725f53c4a659.jpg)](https://coinone.co.kr) | coinone | [CoinOne](https://coinone.co.kr) | 2 | [API](https://doc.coinone.co.kr) | | |
| [![coinspot](https://user-images.githubusercontent.com/1294454/28208429-3cacdf9a-6896-11e7-854e-4c79a772a30f.jpg)](https://www.coinspot.com.au/register?code=PJURCU) | coinspot | [CoinSpot](https://www.coinspot.com.au/register?code=PJURCU) | * | [API](https://www.coinspot.com.au/api) | | |
| [![crex24](https://user-images.githubusercontent.com/1294454/47813922-6f12cc00-dd5d-11e8-97c6-70f957712d47.jpg)](https://crex24.com/?refid=slxsjsjtil8xexl9hksr) | crex24 | [CREX24](https://crex24.com/?refid=slxsjsjtil8xexl9hksr) | 2 | [API](https://docs.crex24.com/trade-api/v2) | | |
| [![currencycom](https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg)](https://currency.com/trading/signup?c=362jaimv&pid=referral) | currencycom | [Currency.com](https://currency.com/trading/signup?c=362jaimv&pid=referral) | 1 | [API](https://currency.com/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![deribit](https://user-images.githubusercontent.com/1294454/41933112-9e2dd65a-798b-11e8-8440-5bab2959fcb8.jpg)](https://www.deribit.com/reg-1189.4038) | deribit | [Deribit](https://www.deribit.com/reg-1189.4038) | 2 | [API](https://docs.deribit.com/v2) | | |
| [![digifinex](https://user-images.githubusercontent.com/51840849/87443315-01283a00-c5fe-11ea-8628-c2a0feaf07ac.jpg)](https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp) | digifinex | [DigiFinex](https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp) | 3 | [API](https://docs.digifinex.com) | | |
| [![dsx](https://user-images.githubusercontent.com/51840849/76909626-cb2bb100-68bc-11ea-99e0-28ba54f04792.jpg)](https://dsxglobal.com) | dsx | [DSX](https://dsxglobal.com) | 3 | [API](https://dsxglobal.com/developers/publicApi) | | |
| [![eterbase](https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg)](https://eterbase.exchange/invite/1wjjh4Pe) | eterbase | [Eterbase](https://eterbase.exchange/invite/1wjjh4Pe) | 1 | [API](https://developers.eterbase.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
| [![exmo](https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg)](https://exmo.me/?ref=131685) | exmo | [EXMO](https://exmo.me/?ref=131685) | 1.1 | [API](https://exmo.me/en/api_doc?ref=131685) | | |
| [![exx](https://user-images.githubusercontent.com/1294454/37770292-fbf613d0-2de4-11e8-9f79-f2dc451b8ccb.jpg)](https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1) | exx | [EXX](https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1) | * | [API](https://www.exx.com/help/restApi) | | |
| [![fcoin](https://user-images.githubusercontent.com/1294454/42244210-c8c42e1e-7f1c-11e8-8710-a5fb63b165c4.jpg)](https://www.fcoin.com/i/Z5P7V) | fcoin | [FCoin](https://www.fcoin.com/i/Z5P7V) | 2 | [API](https://developer.fcoin.com) | | |
| [![fcoinjp](https://user-images.githubusercontent.com/1294454/54219174-08b66b00-4500-11e9-862d-f522d0fe08c6.jpg)](https://www.fcoinjp.com) | fcoinjp | [FCoinJP](https://www.fcoinjp.com) | 2 | [API](https://developer.fcoin.com) | | |
| [![flowbtc](https://user-images.githubusercontent.com/51840849/87443317-01c0d080-c5fe-11ea-95c2-9ebe1a8fafd9.jpg)](https://www.flowbtc.com.br) | flowbtc | [flowBTC](https://www.flowbtc.com.br) | 1 | [API](https://www.flowbtc.com.br/api.html) | | |
| [![foxbit](https://user-images.githubusercontent.com/51840849/87443320-01c0d080-c5fe-11ea-92e2-4ef56d32b026.jpg)](https://foxbit.com.br/exchange) | foxbit | [FoxBit](https://foxbit.com.br/exchange) | 1 | [API](https://foxbit.com.br/api/) | | |
| [![ftx](https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg)](https://ftx.com/#a=1623029) | ftx | [FTX](https://ftx.com/#a=1623029) | * | [API](https://github.com/ftexchange/ftx) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![gateio](https://user-images.githubusercontent.com/1294454/31784029-0313c702-b509-11e7-9ccc-bc0da6a0e435.jpg)](https://www.gate.io/signup/2436035) | gateio | [Gate.io](https://www.gate.io/signup/2436035) | 2 | [API](https://gate.io/api2) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![gemini](https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg)](https://gemini.com/) | gemini | [Gemini](https://gemini.com/) | 1 | [API](https://docs.gemini.com/rest-api) | | |
| [![hbtc](https://user-images.githubusercontent.com/51840849/80134449-70663300-85a7-11ea-8942-e204cdeaab5d.jpg)](https://www.hbtc.com/register/O2S8NS) | hbtc | [HBTC](https://www.hbtc.com/register/O2S8NS) | 1 | [API](https://github.com/bhexopen/BHEX-OpenApi/tree/master/doc) | | |
| [![hitbtc](https://user-images.githubusercontent.com/1294454/27766555-8eaec20e-5edc-11e7-9c5b-6dc69fc42f5e.jpg)](https://hitbtc.com/?ref_id=5a5d39a65d466) | hitbtc | [HitBTC](https://hitbtc.com/?ref_id=5a5d39a65d466) | 2 | [API](https://api.hitbtc.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![hollaex](https://user-images.githubusercontent.com/1294454/75841031-ca375180-5ddd-11ea-8417-b975674c23cb.jpg)](https://pro.hollaex.com/signup?affiliation_code=QSWA6G) | hollaex | [HollaEx](https://pro.hollaex.com/signup?affiliation_code=QSWA6G) | 1 | [API](https://apidocs.hollaex.com) | | |
| [![huobijp](https://user-images.githubusercontent.com/1294454/85734211-85755480-b705-11ea-8b35-0b7f1db33a2f.jpg)](https://www.huobi.co.jp/register/?invite_code=znnq3) | huobijp | [Huobi Japan](https://www.huobi.co.jp/register/?invite_code=znnq3) | 1 | [API](https://api-doc.huobi.co.jp) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![huobipro](https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg)](https://www.huobi.com/en-us/topic/invited/?invite_code=rwrd3) | huobipro | [Huobi Pro](https://www.huobi.com/en-us/topic/invited/?invite_code=rwrd3) | 1 | [API](https://huobiapi.github.io/docs/spot/v1/cn/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![huobiru](https://user-images.githubusercontent.com/1294454/52978816-e8552e00-33e3-11e9-98ed-845acfece834.jpg)](https://www.huobi.com.ru/invite?invite_code=esc74) | huobiru | [Huobi Russia](https://www.huobi.com.ru/invite?invite_code=esc74) | 1 | [API](https://github.com/cloudapidoc/API_Docs_en) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![ice3x](https://user-images.githubusercontent.com/51840849/87460809-1dd06c00-c616-11ea-98ad-7d5e1cb7fcdd.jpg)](https://ice3x.com?ref=14341802) | ice3x | [ICE3X](https://ice3x.com?ref=14341802) | 1 | [API](https://ice3x.co.za/ice-cubed-bitcoin-exchange-api-documentation-1-june-2017) | | |
| [![idex](https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg)](https://idex.io) | idex | [IDEX](https://idex.io) | 2 | [API](https://docs.idex.io/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![independentreserve](https://user-images.githubusercontent.com/51840849/87182090-1e9e9080-c2ec-11ea-8e49-563db9a38f37.jpg)](https://www.independentreserve.com) | independentreserve | [Independent Reserve](https://www.independentreserve.com) | * | [API](https://www.independentreserve.com/API) | | |
| [![indodax](https://user-images.githubusercontent.com/51840849/87070508-9358c880-c221-11ea-8dc5-5391afbbb422.jpg)](https://indodax.com/ref/testbitcoincoid/1) | indodax | [INDODAX](https://indodax.com/ref/testbitcoincoid/1) | 2.0 | [API](https://github.com/btcid/indodax-official-api-docs) | | |
| [![itbit](https://user-images.githubusercontent.com/1294454/27822159-66153620-60ad-11e7-89e7-005f6d7f3de0.jpg)](https://www.itbit.com) | itbit | [itBit](https://www.itbit.com) | 1 | [API](https://api.itbit.com/docs) | | |
| [![kraken](https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg)](https://www.kraken.com) | kraken | [Kraken](https://www.kraken.com) | 0 | [API](https://www.kraken.com/features/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![kucoin](https://user-images.githubusercontent.com/51840849/87295558-132aaf80-c50e-11ea-9801-a2fb0c57c799.jpg)](https://www.kucoin.com/?rcode=E5wkqe) | kucoin | [KuCoin](https://www.kucoin.com/?rcode=E5wkqe) | 2 | [API](https://docs.kucoin.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![kuna](https://user-images.githubusercontent.com/51840849/87153927-f0578b80-c2c0-11ea-84b6-74612568e9e1.jpg)](https://kuna.io?r=kunaid-gvfihe8az7o4) | kuna | [Kuna](https://kuna.io?r=kunaid-gvfihe8az7o4) | 2 | [API](https://kuna.io/documents/api) | | |
| [![lakebtc](https://user-images.githubusercontent.com/1294454/28074120-72b7c38a-6660-11e7-92d9-d9027502281d.jpg)](https://www.lakebtc.com) | lakebtc | [LakeBTC](https://www.lakebtc.com) | 2 | [API](https://www.lakebtc.com/s/api_v2) | | |
| [![latoken](https://user-images.githubusercontent.com/1294454/61511972-24c39f00-aa01-11e9-9f7c-471f1d6e5214.jpg)](https://latoken.com) | latoken | [Latoken](https://latoken.com) | 1 | [API](https://api.latoken.com) | | |
| [![lbank](https://user-images.githubusercontent.com/1294454/38063602-9605e28a-3302-11e8-81be-64b1e53c4cfb.jpg)](https://www.lbex.io/invite?icode=7QCY) | lbank | [LBank](https://www.lbex.io/invite?icode=7QCY) | 1 | [API](https://github.com/LBank-exchange/lbank-official-api-docs) | | |
| [![liquid](https://user-images.githubusercontent.com/1294454/45798859-1a872600-bcb4-11e8-8746-69291ce87b04.jpg)](https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976) | liquid | [Liquid](https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976) | 2 | [API](https://developers.liquid.com) | | |
| [![livecoin](https://user-images.githubusercontent.com/1294454/27980768-f22fc424-638a-11e7-89c9-6010a54ff9be.jpg)](https://livecoin.net/?from=Livecoin-CQ1hfx44) | livecoin | [LiveCoin](https://livecoin.net/?from=Livecoin-CQ1hfx44) | * | [API](https://www.livecoin.net/api?lang=en) | | |
| [![luno](https://user-images.githubusercontent.com/1294454/27766607-8c1a69d8-5ede-11e7-930c-540b5eb9be24.jpg)](https://www.luno.com/invite/44893A) | luno | [luno](https://www.luno.com/invite/44893A) | 1 | [API](https://www.luno.com/en/api) | | |
| [![lykke](https://user-images.githubusercontent.com/1294454/34487620-3139a7b0-efe6-11e7-90f5-e520cef74451.jpg)](https://www.lykke.com) | lykke | [Lykke](https://www.lykke.com) | 1 | [API](https://hft-api.lykke.com/swagger/ui/) | | |
| [![mercado](https://user-images.githubusercontent.com/1294454/27837060-e7c58714-60ea-11e7-9192-f05e86adb83f.jpg)](https://www.mercadobitcoin.com.br) | mercado | [Mercado Bitcoin](https://www.mercadobitcoin.com.br) | 3 | [API](https://www.mercadobitcoin.com.br/api-doc) | | |
| [![mixcoins](https://user-images.githubusercontent.com/51840849/87460810-1dd06c00-c616-11ea-9276-956f400d6ffa.jpg)](https://mixcoins.com) | mixcoins | [MixCoins](https://mixcoins.com) | 1 | [API](https://mixcoins.com/help/api/) | | |
| [![novadax](https://user-images.githubusercontent.com/1294454/92337550-2b085500-f0b3-11ea-98e7-5794fb07dd3b.jpg)](https://www.novadax.com.br/?s=ccxt) | novadax | [NovaDAX](https://www.novadax.com.br/?s=ccxt) | 1 | [API](https://doc.novadax.com/pt-BR/) | | |
| [![oceanex](https://user-images.githubusercontent.com/1294454/58385970-794e2d80-8001-11e9-889c-0567cd79b78e.jpg)](https://oceanex.pro/signup?referral=VE24QX) | oceanex | [OceanEx](https://oceanex.pro/signup?referral=VE24QX) | 1 | [API](https://api.oceanex.pro/doc/v1) | | |
| [![okcoin](https://user-images.githubusercontent.com/51840849/87295551-102fbf00-c50e-11ea-90a9-462eebba5829.jpg)](https://www.okcoin.com/account/register?flag=activity&channelId=600001513) | okcoin | [OKCoin](https://www.okcoin.com/account/register?flag=activity&channelId=600001513) | 3 | [API](https://www.okcoin.com/docs/en/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![okex](https://user-images.githubusercontent.com/1294454/32552768-0d6dd3c6-c4a6-11e7-90f8-c043b64756a7.jpg)](https://www.okex.com/join/1888677) | okex | [OKEX](https://www.okex.com/join/1888677) | 3 | [API](https://www.okex.com/docs/en/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![paymium](https://user-images.githubusercontent.com/51840849/87153930-f0f02200-c2c0-11ea-9c0a-40337375ae89.jpg)](https://www.paymium.com/page/sign-up?referral=eDAzPoRQFMvaAB8sf-qj) | paymium | [Paymium](https://www.paymium.com/page/sign-up?referral=eDAzPoRQFMvaAB8sf-qj) | 1 | [API](https://github.com/Paymium/api-documentation) | | |
| [![phemex](https://user-images.githubusercontent.com/1294454/85225056-221eb600-b3d7-11ea-930d-564d2690e3f6.jpg)](https://phemex.com/register?referralCode=EDNVJ) | phemex | [Phemex](https://phemex.com/register?referralCode=EDNVJ) | 1 | [API](https://github.com/phemex/phemex-api-docs) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![poloniex](https://user-images.githubusercontent.com/1294454/27766817-e9456312-5ee6-11e7-9b3c-b628ca5626a5.jpg)](https://poloniex.com/signup?c=UBFZJRPJ) | poloniex | [Poloniex](https://poloniex.com/signup?c=UBFZJRPJ) | * | [API](https://docs.poloniex.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![probit](https://user-images.githubusercontent.com/51840849/79268032-c4379480-7ea2-11ea-80b3-dd96bb29fd0d.jpg)](https://www.probit.com/r/34608773) | probit | [ProBit](https://www.probit.com/r/34608773) | 1 | [API](https://docs-en.probit.com) | | |
| [![qtrade](https://user-images.githubusercontent.com/51840849/80491487-74a99c00-896b-11ea-821e-d307e832f13e.jpg)](https://qtrade.io/?ref=BKOQWVFGRH2C) | qtrade | [qTrade](https://qtrade.io/?ref=BKOQWVFGRH2C) | 1 | [API](https://qtrade-exchange.github.io/qtrade-docs) | | |
| [![rightbtc](https://user-images.githubusercontent.com/51840849/87182092-1f372700-c2ec-11ea-8f9e-01b4d3ff8941.jpg)](https://www.rightbtc.com) | rightbtc | [RightBTC](https://www.rightbtc.com) | * | [API](https://docs.rightbtc.com/api/) | | |
| [![ripio](https://user-images.githubusercontent.com/1294454/94507548-a83d6a80-0218-11eb-9998-28b9cec54165.jpg)](https://exchange.ripio.com) | ripio | [Ripio](https://exchange.ripio.com) | 1 | [API](https://exchange.ripio.com/en/api/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![southxchange](https://user-images.githubusercontent.com/1294454/27838912-4f94ec8a-60f6-11e7-9e5d-bbf9bd50a559.jpg)](https://www.southxchange.com) | southxchange | [SouthXchange](https://www.southxchange.com) | * | [API](https://www.southxchange.com/Home/Api) | | |
| [![stex](https://user-images.githubusercontent.com/1294454/69680782-03fd0b80-10bd-11ea-909e-7f603500e9cc.jpg)](https://app.stex.com?ref=36416021) | stex | [STEX](https://app.stex.com?ref=36416021) | 3 | [API](https://help.stex.com/en/collections/1593608-api-v3-documentation) | | |
| [![surbitcoin](https://user-images.githubusercontent.com/1294454/27991511-f0a50194-6481-11e7-99b5-8f02932424cc.jpg)](https://surbitcoin.com) | surbitcoin | [SurBitcoin](https://surbitcoin.com) | 1 | [API](https://blinktrade.com/docs) | | |
| [![therock](https://user-images.githubusercontent.com/1294454/27766869-75057fa2-5ee9-11e7-9a6f-13e641fa4707.jpg)](https://therocktrading.com) | therock | [TheRockTrading](https://therocktrading.com) | 1 | [API](https://api.therocktrading.com/doc/v1/index.html) | | |
| [![tidebit](https://user-images.githubusercontent.com/51840849/87460811-1e690280-c616-11ea-8652-69f187305add.jpg)](http://bit.ly/2IX0LrM) | tidebit | [TideBit](http://bit.ly/2IX0LrM) | 2 | [API](https://www.tidebit.com/documents/api/guide) | | |
| [![tidex](https://user-images.githubusercontent.com/1294454/30781780-03149dc4-a12e-11e7-82bb-313b269d24d4.jpg)](https://tidex.com/exchange/?ref=57f5638d9cd7) | tidex | [Tidex](https://tidex.com/exchange/?ref=57f5638d9cd7) | 3 | [API](https://tidex.com/exchange/public-api) | | |
| [![timex](https://user-images.githubusercontent.com/1294454/70423869-6839ab00-1a7f-11ea-8f94-13ae72c31115.jpg)](https://timex.io/?refcode=1x27vNkTbP1uwkCck) | timex | [TimeX](https://timex.io/?refcode=1x27vNkTbP1uwkCck) | 1 | [API](https://docs.timex.io) | | |
| [![upbit](https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg)](https://upbit.com) | upbit | [Upbit](https://upbit.com) | 1 | [API](https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) |
| [![vaultoro](https://user-images.githubusercontent.com/1294454/27766880-f205e870-5ee9-11e7-8fe2-0d5b15880752.jpg)](https://www.vaultoro.com) | vaultoro | [Vaultoro](https://www.vaultoro.com) | 1 | [API](https://api.vaultoro.com) | | |
| [![vbtc](https://user-images.githubusercontent.com/1294454/27991481-1f53d1d8-6481-11e7-884e-21d17e7939db.jpg)](https://vbtc.exchange) | vbtc | [VBTC](https://vbtc.exchange) | 1 | [API](https://blinktrade.com/docs) | | |
| [![vcc](https://user-images.githubusercontent.com/1294454/100545356-8427f500-326c-11eb-9539-7d338242d61b.jpg)](https://vcc.exchange?ref=l4xhrH) | vcc | [VCC Exchange](https://vcc.exchange?ref=l4xhrH) | 3 | [API](https://vcc.exchange/api) | | |
| [![wavesexchange](https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg)](https://waves.exchange) | wavesexchange | [Waves.Exchange](https://waves.exchange) | * | [API](https://docs.waves.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
| [![whitebit](https://user-images.githubusercontent.com/1294454/66732963-8eb7dd00-ee66-11e9-849b-10d9282bb9e0.jpg)](https://whitebit.com/referral/d9bdf40e-28f2-4b52-b2f9-cd1415d82963) | whitebit | [WhiteBit](https://whitebit.com/referral/d9bdf40e-28f2-4b52-b2f9-cd1415d82963) | 2 | [API](https://documenter.getpostman.com/view/7473075/Szzj8dgv?version=latest) | | |
| [![xbtce](https://user-images.githubusercontent.com/1294454/28059414-e235970c-662c-11e7-8c3a-08e31f78684b.jpg)](https://xbtce.com/?agent=XX97BTCXXXG687021000B) | xbtce | [xBTCe](https://xbtce.com/?agent=XX97BTCXXXG687021000B) | 1 | [API](https://www.xbtce.com/tradeapi) | | |
| [![xena](https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg)](https://xena.exchange) | xena | [Xena Exchange](https://xena.exchange) | * | [API](https://support.xena.exchange/support/solutions/44000808700) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | |
| [![yobit](https://user-images.githubusercontent.com/1294454/27766910-cdcbfdae-5eea-11e7-9859-03fea873272d.jpg)](https://www.yobit.net) | yobit | [YoBit](https://www.yobit.net) | 3 | [API](https://www.yobit.net/en/api/) | | |
| [![zaif](https://user-images.githubusercontent.com/1294454/27766927-39ca2ada-5eeb-11e7-972f-1b4199518ca6.jpg)](https://zaif.jp) | zaif | [Zaif](https://zaif.jp) | 1 | [API](https://techbureau-api-document.readthedocs.io/ja/latest/index.html) | | |
| [![zb](https://user-images.githubusercontent.com/1294454/32859187-cd5214f0-ca5e-11e7-967d-96568e2e2bd1.jpg)](https://www.zb.com) | zb | [ZB](https://www.zb.com) | 1 | [API](https://www.zb.com/i/developer) | | |
The list above is updated frequently, new crypto markets, exchanges, bug fixes, and API endpoints are introduced on a regular basis. See the [Manual](https://github.com/ccxt/ccxt/wiki) for more details. If you can't find a cryptocurrency exchange in the list above and want it to be added, post a link to it by opening an issue here on GitHub or send us an email.
The library is under [MIT license](https://github.com/ccxt/ccxt/blob/master/LICENSE.txt), that means it's absolutely free for any developer to build commercial and opensource software on top of it, but use it at your own risk with no warranties, as is.
---
## Install
The easiest way to install the CCXT library is to use a package manager:
- [ccxt in **NPM**](https://www.npmjs.com/package/ccxt) (JavaScript / Node v7.6+)
- [ccxt in **PyPI**](https://pypi.python.org/pypi/ccxt) (Python 3.5.3+)
- [ccxt in **Packagist/Composer**](https://packagist.org/packages/ccxt/ccxt) (PHP 5.4+)
This library is shipped as an all-in-one module implementation with minimalistic dependencies and requirements:
- [`js/`](https://github.com/ccxt/ccxt/blob/master/js/) in JavaScript
- [`python/`](https://github.com/ccxt/ccxt/blob/master/python/) in Python (generated from JS)
- [`php/`](https://github.com/ccxt/ccxt/blob/master/php/) in PHP (generated from JS)
You can also clone it into your project directory from [ccxt GitHub repository](https://github.com/ccxt/ccxt):
```shell
git clone https://github.com/ccxt/ccxt.git
```
### JavaScript (NPM)
JavaScript version of CCXT works in both Node and web browsers. Requires ES6 and `async/await` syntax support (Node 7.6.0+). When compiling with Webpack and Babel, make sure it is [not excluded](https://github.com/ccxt/ccxt/issues/225#issuecomment-331905178) in your `babel-loader` config.
[ccxt in **NPM**](https://www.npmjs.com/package/ccxt)
```shell
npm install ccxt
```
```JavaScript
var ccxt = require ('ccxt')
console.log (ccxt.exchanges) // print all available exchanges
```
### JavaScript (for use with the `<script>` tag):
All-in-one browser bundle (dependencies included), served from a CDN of your choice:
* jsDelivr: https://cdn.jsdelivr.net/npm/ccxt@1.38.75/dist/ccxt.browser.js
* unpkg: https://unpkg.com/ccxt@1.38.75/dist/ccxt.browser.js
CDNs are not updated in real-time and may have delays. Defaulting to the most recent version without specifying the version number is not recommended. Please, keep in mind that we are not responsible for the correct operation of those CDN servers.
```HTML
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/ccxt@1.38.75/dist/ccxt.browser.js"></script>
```
Creates a global `ccxt` object:
```JavaScript
console.log (ccxt.exchanges) // print all available exchanges
```
### Python
[ccxt in **PyPI**](https://pypi.python.org/pypi/ccxt)
```shell
pip install ccxt
```
```Python
import ccxt
print(ccxt.exchanges) # print a list of all available exchange classes
```
The library supports concurrent asynchronous mode with asyncio and async/await in Python 3.5.3+
```Python
import ccxt.async_support as ccxt # link against the asynchronous version of ccxt
```
### PHP
[ccxt in PHP with **Packagist/Composer**](https://packagist.org/packages/ccxt/ccxt) (PHP 5.4+)
It requires common PHP modules:
- cURL
- mbstring (using UTF-8 is highly recommended)
- PCRE
- iconv
- gmp (this is a built-in extension as of PHP 7.2+)
```PHP
include "ccxt.php";
var_dump (\ccxt\Exchange::$exchanges); // print a list of all available exchange classes
```
### Docker
You can get CCXT installed in a container along with all the supported languages and dependencies. This may be useful if you want to contribute to CCXT (e.g. run the build scripts and tests — please see the [Contributing](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md) document for the details on that).
Using `docker-compose` (in the cloned CCXT repository):
```shell
docker-compose run --rm ccxt
```
You don't need the Docker image if you're not going to develop CCXT. If you just want to use CCXT – just install it as a regular package into your project.
---
## Documentation
Read the [Manual](https://github.com/ccxt/ccxt/wiki) for more details.
## Usage
### Intro
The CCXT library consists of a public part and a private part. Anyone can use the public part immediately after installation. Public APIs provide unrestricted access to public information for all exchange markets without the need to register a user account or have an API key.
Public APIs include the following:
- market data
- instruments/trading pairs
- price feeds (exchange rates)
- order books
- trade history
- tickers
- OHLC(V) for charting
- other public endpoints
In order to trade with private APIs you need to obtain API keys from an exchange's website. It usually means signing up to the exchange and creating API keys for your account. Some exchanges require personal info or identification. Sometimes verification may be necessary as well. In this case you will need to register yourself, this library will not create accounts or API keys for you. Some exchanges expose API endpoints for registering an account, but most exchanges don't. You will have to sign up and create API keys on their websites.
Private APIs allow the following:
- manage personal account info
- query account balances
- trade by making market and limit orders
- deposit and withdraw fiat and crypto funds
- query personal orders
- get ledger history
- transfer funds between accounts
- use merchant services
This library implements full public and private REST APIs for all exchanges. WebSocket and FIX implementations in JavaScript, PHP, Python are available in [CCXT Pro](https://ccxt.pro), which is a professional addon to CCXT with support for WebSocket streams.
The CCXT library supports both camelcase notation (preferred in JavaScript) and underscore notation (preferred in Python and PHP), therefore all methods can be called in either notation or coding style in any language.
```JavaScript
// both of these notations work in JavaScript/Python/PHP
exchange.methodName () // camelcase pseudocode
exchange.method_name () // underscore pseudocode
```
Read the [Manual](https://github.com/ccxt/ccxt/wiki) for more details.
### JavaScript
```JavaScript
'use strict';
const ccxt = require ('ccxt');
(async function () {
let kraken = new ccxt.kraken ()
let bitfinex = new ccxt.bitfinex ({ verbose: true })
let huobipro = new ccxt.huobipro ()
let okcoinusd = new ccxt.okcoinusd ({
apiKey: 'YOUR_PUBLIC_API_KEY',
secret: 'YOUR_SECRET_PRIVATE_KEY',
})
const exchangeId = 'binance'
, exchangeClass = ccxt[exchangeId]
, exchange = new exchangeClass ({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
'timeout': 30000,
'enableRateLimit': true,
})
console.log (kraken.id, await kraken.loadMarkets ())
console.log (bitfinex.id, await bitfinex.loadMarkets ())
console.log (huobipro.id, await huobipro.loadMarkets ())
console.log (kraken.id, await kraken.fetchOrderBook (kraken.symbols[0]))
console.log (bitfinex.id, await bitfinex.fetchTicker ('BTC/USD'))
console.log (huobipro.id, await huobipro.fetchTrades ('ETH/USDT'))
console.log (okcoinusd.id, await okcoinusd.fetchBalance ())
// sell 1 BTC/USD for market price, sell a bitcoin for dollars immediately
console.log (okcoinusd.id, await okcoinusd.createMarketSellOrder ('BTC/USD', 1))
// buy 1 BTC/USD for $2500, you pay $2500 and receive ฿1 when the order is closed
console.log (okcoinusd.id, await okcoinusd.createLimitBuyOrder ('BTC/USD', 1, 2500.00))
// pass/redefine custom exchange-specific order params: type, amount, price or whatever
// use a custom order type
bitfinex.createLimitSellOrder ('BTC/USD', 1, 10, { 'type': 'trailing-stop' })
}) ();
```
### Python
```Python
# coding=utf-8
import ccxt
hitbtc = ccxt.hitbtc({'verbose': True})
bitmex = ccxt.bitmex()
huobipro = ccxt.huobipro()
exmo = ccxt.exmo({
'apiKey': 'YOUR_PUBLIC_API_KEY',
'secret': 'YOUR_SECRET_PRIVATE_KEY',
})
kraken = ccxt.kraken({
'apiKey': 'YOUR_PUBLIC_API_KEY',
'secret': 'YOUR_SECRET_PRIVATE_KEY',
})
exchange_id = 'binance'
exchange_class = getattr(ccxt, exchange_id)
exchange = exchange_class({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
'timeout': 30000,
'enableRateLimit': True,
})
hitbtc_markets = hitbtc.load_markets()
print(hitbtc.id, hitbtc_markets)
print(bitmex.id, bitmex.load_markets())
print(huobipro.id, huobipro.load_markets())
print(hitbtc.fetch_order_book(hitbtc.symbols[0]))
print(bitmex.fetch_ticker('BTC/USD'))
print(huobipro.fetch_trades('LTC/USDT'))
print(exmo.fetch_balance())
# sell one ฿ for market price and receive $ right now
print(exmo.id, exmo.create_market_sell_order('BTC/USD', 1))
# limit buy BTC/EUR, you pay €2500 and receive ฿1 when the order is closed
print(exmo.id, exmo.create_limit_buy_order('BTC/EUR', 1, 2500.00))
# pass/redefine custom exchange-specific order params: type, amount, price, flags, etc...
kraken.create_market_buy_order('BTC/USD', 1, {'trading_agreement': 'agree'})
```
### PHP
```PHP
include 'ccxt.php';
$poloniex = new \ccxt\poloniex ();
$bittrex = new \ccxt\bittrex (array ('verbose' => true));
$quoinex = new \ccxt\quoinex ();
$zaif = new \ccxt\zaif (array (
'apiKey' => 'YOUR_PUBLIC_API_KEY',
'secret' => 'YOUR_SECRET_PRIVATE_KEY',
));
$hitbtc = new \ccxt\hitbtc (array (
'apiKey' => 'YOUR_PUBLIC_API_KEY',
'secret' => 'YOUR_SECRET_PRIVATE_KEY',
));
$exchange_id = 'binance';
$exchange_class = "\\ccxt\\$exchange_id";
$exchange = new $exchange_class (array (
'apiKey' => 'YOUR_API_KEY',
'secret' => 'YOUR_SECRET',
'timeout' => 30000,
'enableRateLimit' => true,
));
$poloniex_markets = $poloniex->load_markets ();
var_dump ($poloniex_markets);
var_dump ($bittrex->load_markets ());
var_dump ($quoinex->load_markets ());
var_dump ($poloniex->fetch_order_book ($poloniex->symbols[0]));
var_dump ($bittrex->fetch_trades ('BTC/USD'));
var_dump ($quoinex->fetch_ticker ('ETH/EUR'));
var_dump ($zaif->fetch_ticker ('BTC/JPY'));
var_dump ($zaif->fetch_balance ());
// sell 1 BTC/JPY for market price, you pay ¥ and receive ฿ immediately
var_dump ($zaif->id, $zaif->create_market_sell_order ('BTC/JPY', 1));
// buy BTC/JPY, you receive ฿1 for ¥285000 when the order closes
var_dump ($zaif->id, $zaif->create_limit_buy_order ('BTC/JPY', 1, 285000));
// set a custom user-defined id to your order
$hitbtc->create_order ('BTC/USD', 'limit', 'buy', 1, 3000, array ('clientOrderId' => '123'));
```
## Contributing
Please read the [CONTRIBUTING](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md) document before making changes that you would like adopted in the code. Also, read the [Manual](https://github.com/ccxt/ccxt/wiki) for more details.
## Support Developer Team
We are investing a significant amount of time into the development of this library. If CCXT made your life easier and you want to help us improve it further, or if you want to speed up development of new features and exchanges, please support us with a tip. We appreciate all contributions!
### Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.
[[Become a sponsor](https://opencollective.com/ccxt#sponsor)]
<a href="https://opencollective.com/ccxt/tiers/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/sponsor/9/avatar.svg"></a>
### Supporters
Support this project by becoming a supporter. Your avatar will show up here with a link to your website.
[[Become a supporter](https://opencollective.com/ccxt#supporter)]
<a href="https://opencollective.com/ccxt/tiers/supporter/0/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/0/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/1/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/1/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/2/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/2/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/3/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/3/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/4/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/4/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/5/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/5/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/6/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/6/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/7/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/7/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/8/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/8/avatar.svg"></a>
<a href="https://opencollective.com/ccxt/tiers/supporter/9/website" target="_blank"><img src="https://opencollective.com/ccxt/tiers/supporter/9/avatar.svg"></a>
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/ccxt#backer)]
<a href="https://opencollective.com/ccxt#backers" target="_blank"><img src="https://opencollective.com/ccxt/tiers/backer.svg?width=890"></a>
### Crypto
```
ETH 0x26a3CB49578F07000575405a57888681249c35Fd (ETH only)
BTC 33RmVRfhK2WZVQR1R83h2e9yXoqRNDvJva
BCH 1GN9p233TvNcNQFthCgfiHUnj5JRKEc2Ze
LTC LgM5hkvPzytEFfQGGDFGKYkE743jwUA7qv
```
Thank you!
## Social
- [Follow us on Twitter](https://twitter.com/ccxt_official)
- [Read our blog on Medium](https://medium.com/@ccxt)
- <sub><sub>[![Discord](https://img.shields.io/discord/690203284119617602?logo=discord&logoColor=white)](https://discord.gg/dhzSKYU)</sub></sub>
## Contact Us
For business inquiries: info@ccxt.trade
@@ -8,7 +8,10 @@
const fs = require ('fs')
, countries = require ('./countries')
, asTable = require ('as-table')
, asTable = require ('as-table').configure ({
delimiter: '|',
print: (x) => ' ' + x + ' '
})
, execSync = require ('child_process').execSync
, log = require ('ololog').unlimited
, ansi = require ('ansicolor').nice
@@ -79,194 +82,190 @@ function createExchanges (ids) {
return ccxt.indexBy (ids.map (createExchange), 'id')
}
// // ----------------------------------------------------------------------------
// // strategically placed exactly here (we can require it AFTER the export)
// const ccxt = require ('../ccxt.js')
// // ----------------------------------------------------------------------------
// // create exchanges
// const createExchange = (id) => {
// ccxt[id].prototype.checkRequiredDependencies = () => {} // suppress it
// return new (ccxt)[id] ()
// }
// const exchanges = ccxt.indexBy (ids.map (createExchange), 'id')
// ----------------------------------------------------------------------------
const ccxtCertifiedBadge = '[![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification)'
, ccxtProBadge = '[![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro)'
// ----------------------------------------------------------------------------
function createMarkdownListOfExchanges (exchanges) {
return exchanges.map ((exchange) => {
const www = exchange.urls.www
, url = exchange.urls.referral || www
, doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
, version = exchange.version ? exchange.version.replace (/[^0-9\.]+/, '') : '\*'
return {
'logo': '[![' + exchange.id + '](' + exchange.urls.logo + ')](' + url + ')',
'id': exchange.id,
'name': '[' + exchange.name + '](' + url + ')',
'ver': version,
'doc': '[API](' + doc + ')',
'certified': exchange.certified ? ccxtCertifiedBadge : '',
'pro': exchange.pro ? ccxtProBadge : '',
}
})
}
// ----------------------------------------------------------------------------
const sortByCountry = (a, b) => {
if (a['country / region'] > b['country / region']) {
return 1
} else if (a['country / region'] < b['country / region']) {
return -1;
} else {
if (a['id'] > b['id']) {
return 1;
} else if (a['id'] < b['id']) {
return -1;
} else {
return 0;
}
}
}
// ----------------------------------------------------------------------------
function createMarkdownListOfExchangesByCountries (exchanges) {
const exchangesByCountries = []
keys (countries).forEach (code => {
exchanges.forEach (exchange => {
const website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
, url = exchange.urls.referral || website
, doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
, version = exchange.version ? exchange.version.replace (/[^0-9\.]+/, '') : '\*'
const exchangeInCountry =
(Array.isArray (exchange.countries) && exchange.countries.includes (code)) ||
(code === exchange.countries)
if (exchangeInCountry) {
exchangesByCountries.push ({
'country / region': countries[code],
'logo': '[![' + exchange.id + '](' + exchange.urls.logo + ')](' + url + ')',
'id': exchange.id,
'name': '[' + exchange.name + '](' + url + ')',
'ver': version,
'doc': '[API](' + doc + ')',
})
}
})
});
return exchangesByCountries.sort (sortByCountry)
}
// ----------------------------------------------------------------------------
function createMarkdownTable (array, markdownMethod, centeredColumns) {
array = markdownMethod (array)
const table = asTable (array)
const lines = table.split ("\n")
//
// asTable creates a header underline like
//
// logo | id | name | ver | doc | certified | pro
// ------------------------------------------------
//
// we fix it to match markdown underline like
//
// logo | id | name | ver | doc | certified | pro
// ------|----|------|-----|-----|-----------|-----
//
const underline = lines[0].replace (/[^\|]/g, '-')
//
// ver and doc columns should be centered so we convert it to
//
// logo | id | name | ver | doc | certified | pro
// ------|----|------|:---:|:---:|-----------|-----
//
const columns = underline.split ('|')
for (const i of centeredColumns) {
columns[i] = ':' + columns[i].slice (1, columns[i].length - 1) + ':'
}
lines.splice (1, 1, columns.join ('|'))
//
// prepend and append a vertical bar to each line
//
// | logo | id | name | ver | doc | certified | pro |
// |------|----|------|:---:|:---:|-----------|-----|
//
return lines.map (line => '|' + line + '|').join ("\n")
}
// ----------------------------------------------------------------------------
// TODO: REWRITE THIS ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
function exportSupportedAndCertifiedExchanges (exchanges, { allExchangesPaths, certifiedExchangesPaths, exchangesByCountriesPaths, proExchangesPaths }) {
// ............................................................................
// markup constants and helper functions
const countryName = (code) => countries[code] || code
const ccxtCertifiedBadge = '[![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification)'
const ccxtProBadge = '[![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro)'
const logoHeading = '&nbsp;'.repeat (7) + 'logo' + '&nbsp;'.repeat (7)
const tableHeadings = [ logoHeading, 'id', 'name', 'ver', 'doc', 'certified', 'pro' ]
const exchangesByCountryHeading = [ 'country / region', ... tableHeadings.slice (0, 5) ]
// ----------------------------------------------------------------------------
// list all supported exchanges
const exchangesNotListedInDocs = [ 'hitbtc2' ]
function makeTableData (exchanges) {
return (
values (exchanges)
.filter (exchange => !exchangesNotListedInDocs.includes (exchange.id))
.map (exchange => {
let logo = exchange.urls['logo']
let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
let url = exchange.urls.referral || website
let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
let version = exchange.version ? exchange.version : '\*'
let matches = version.match (/[^0-9]*([0-9].*)/)
if (matches) {
version = matches[1];
}
return [
'[![' + exchange.id + '](' + logo + ')](' + url + ')',
exchange.id,
'[' + exchange.name + '](' + url + ')',
version,
'[API](' + doc + ')',
exchange.certified ? ccxtCertifiedBadge : '',
exchange.pro ? ccxtProBadge : '',
]
})
)
}
function makeTable (jsonArray) {
let table = asTable.configure ({ 'delimiter': ' | ' }) (jsonArray)
let lines = table.split ("\n")
lines.splice (1,0, lines[0].replace (/[^\|]/g, '-'))
let headerLine = lines[1].split ('|')
headerLine[3] = ':' + headerLine[3].slice (1, headerLine[3].length - 1) + ':'
headerLine[4] = ':' + headerLine[4].slice (1, headerLine[4].length - 1) + ':'
lines[1] = headerLine.join ('|')
return lines.map (line => '|' + line + '|').join ("\n")
}
const arrayOfExchanges = values (exchanges)
if (allExchangesPaths) {
const tableData = makeTableData (exchanges)
const numExchanges = tableData.length
// prepend the table header
tableData.splice (0, 0, tableHeadings)
const exchangesTable = makeTable (tableData)
const beginning = "The CCXT library currently supports the following "
const ending = " cryptocurrency exchange markets and trading APIs:\n\n"
const totalString = beginning + numExchanges + ending
const allExchanges = totalString + exchangesTable + "$1"
const allExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
const supportedExchangesMarkdownTable = createMarkdownTable (arrayOfExchanges, createMarkdownListOfExchanges, [ 3, 4 ])
, numExchanges = arrayOfExchanges.length
, beginning = "The CCXT library currently supports the following "
, ending = " cryptocurrency exchange markets and trading APIs:\n\n"
, totalString = beginning + numExchanges + ending
, allExchangesReplacement = totalString + supportedExchangesMarkdownTable + "$1"
, allExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\| logo[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
for (const path of allExchangesPaths) {
logExportExchanges (path, allExchangesRegex, allExchanges)
logExportExchanges (path, allExchangesRegex, allExchangesReplacement)
}
// logExportExchanges ('README.md', allExchangesRegex, allExchanges)
// logExportExchanges (wikiPath + '/Manual.md', allExchangesRegex, allExchanges)
// logExportExchanges (wikiPath + '/Exchange-Markets.md', allExchangesRegex, allExchanges)
}
if (proExchangesPaths) {
const pro = values (exchanges).filter (exchange => exchange.pro)
const tableData = makeTableData (pro)
const numExchanges = tableData.length
// prepend the table header
tableData.splice (0, 0, tableHeadings)
const exchangesTable = makeTable (tableData)
const beginning = "The CCXT Pro library currently supports the following "
const ending = " cryptocurrency exchange markets and WebSocket trading APIs:\n\n"
const totalString = beginning + numExchanges + ending
const proExchanges = totalString + exchangesTable + "$1"
const proExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
const proExchanges = arrayOfExchanges.filter (exchange => exchange.pro)
, proExchangesMarkdownTable = createMarkdownTable (proExchanges, createMarkdownListOfExchanges, [ 3, 4 ])
, numExchanges = proExchanges.length
, beginning = "The CCXT Pro library currently supports the following "
, ending = " cryptocurrency exchange markets and WebSocket trading APIs:\n\n"
, totalString = beginning + numExchanges + ending
, proExchangesReplacement = totalString + proExchangesMarkdownTable + "$1"
, proExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
for (const path of proExchangesPaths) {
logExportExchanges (path, proExchangesRegex, proExchanges)
logExportExchanges (path, proExchangesRegex, proExchangesReplacement)
}
}
if (certifiedExchangesPaths) {
const certified = values (exchanges).filter (exchange => exchange.certified)
const tableData = makeTableData (certified)
// prepend the table header
tableData.splice (0, 0, tableHeadings)
const certifiedExchangesRegex = new RegExp ("^(## Certified Cryptocurrency Exchanges\n{3})(?:\\|.+\\|$\n)+", 'm')
const certifiedExchangesTable = makeTable (tableData)
const certifiedExchanges = '$1' + certifiedExchangesTable + "\n"
const certifiedExchanges = arrayOfExchanges.filter (exchange => exchange.certified)
, certifiedExchangesMarkdownTable = createMarkdownTable (certifiedExchanges, createMarkdownListOfExchanges, [ 3, 4 ])
, certifiedExchangesReplacement = '$1' + certifiedExchangesMarkdownTable + "\n"
, certifiedExchangesRegex = new RegExp ("^(## Certified Cryptocurrency Exchanges\n{3})(?:\\|.+\\|$\n)+", 'm')
for (const path of certifiedExchangesPaths) {
logExportExchanges (path, certifiedExchangesRegex, certifiedExchanges)
logExportExchanges (path, certifiedExchangesRegex, certifiedExchangesReplacement)
}
// logExportExchanges ('README.md', certifiedExchangesRegex, certifiedExchanges)
}
if (exchangesByCountriesPaths) {
let exchangesByCountries = []
keys (countries).forEach (code => {
let country = countries[code]
let result = []
keys (exchanges).forEach (id => {
let exchange = exchanges[id]
let logo = exchange.urls['logo']
let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
let url = exchange.urls.referral || website
let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
let version = exchange.version ? exchange.version : '\*'
let matches = version.match (/[^0-9]*([0-9].*)/)
if (matches)
version = matches[1];
let shouldInclude = false
if (Array.isArray (exchange.countries)) {
if (exchange.countries.indexOf (code) > -1)
shouldInclude = true
} else {
if (code == exchange.countries)
shouldInclude = true
}
if (shouldInclude) {
let entry = [
country,
'[![' + exchange.id + '](' + logo + ')](' + url + ')',
exchange.id,
'[' + exchange.name + '](' + url + ')',
version,
'[API](' + doc + ')',
// doesn't fit in width
// exchange.certified ? ccxtCertifiedBadge : '',
]
result.push (entry)
}
})
exchangesByCountries = exchangesByCountries.concat (result)
});
const countryKeyIndex = exchangesByCountryHeading.indexOf ('country / region')
exchangesByCountries = exchangesByCountries.sort ((a, b) => {
const countryA = a[countryKeyIndex].toLowerCase ()
const countryB = b[countryKeyIndex].toLowerCase ()
if (countryA > countryB) {
return 1
} else if (countryA < countryB) {
return -1;
} else {
if (a['id'] > b['id'])
return 1;
else if (a['id'] < b['id'])
return -1;
else
return 0;
}
})
exchangesByCountries.splice (0, 0, exchangesByCountryHeading)
const lines = makeTable (exchangesByCountries)
const result = "# Exchanges By Country\n\nThe ccxt library currently supports the following cryptocurrency exchange markets and trading APIs:\n\n" + lines + "\n\n"
const exchangesByCountriesMarkdownTable = createMarkdownTable (arrayOfExchanges, createMarkdownListOfExchangesByCountries, [ 4, 5 ])
const result = "# Exchanges By Country\n\nThe ccxt library currently supports the following cryptocurrency exchange markets and trading APIs:\n\n" + exchangesByCountriesMarkdownTable + "\n\n"
for (const path of exchangesByCountriesPaths) {
fs.truncateSync (path)
fs.writeFileSync (path, result)
@@ -274,7 +273,6 @@ function exportSupportedAndCertifiedExchanges (exchanges, { allExchangesPaths, c
}
}
// TODO: REWRITE THIS ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// ----------------------------------------------------------------------------
function exportExchangeIdsToExchangesJson (exchanges) {
@@ -342,6 +340,7 @@ function flatten (nested, result = []) {
return result
}
// ----------------------------------------------------------------------------
function exportEverything () {
const ids = getIncludedExchangeIds ()
@@ -12,7 +12,9 @@ function replaceInFile (filename, regex, replacement) {
function copyFile (oldName, newName) {
const contents = fs.readFileSync (oldName, 'utf8')
fs.truncateSync (newName)
if (fs.existsSync (newName)) {
fs.truncateSync (newName)
}
fs.writeFileSync (newName, contents)
}
@@ -56,6 +56,7 @@ class Transpiler {
[ /\.implodeParams\s/g, '.implode_params'],
[ /\.extractParams\s/g, '.extract_params'],
[ /\.parseBalance\s/g, '.parse_balance'],
[ /\.parseBalanceResponse\s/g, '.parse_balance_response'],
[ /\.parseOHLCVs\s/g, '.parse_ohlcvs'],
[ /\.parseOHLCV\s/g, '.parse_ohlcv'],
[ /\.parseDate\s/g, '.parse_date'],
@@ -137,6 +138,9 @@ class Transpiler {
[ /\.isJsonEncodedObject\s/g, '.is_json_encoded_object'],
[ /\.setSandboxMode\s/g, '.set_sandbox_mode'],
[ /\.safeCurrencyCode\s/g, '.safe_currency_code'],
[ /\.safeCurrency\s/g, '.safe_currency'],
[ /\.safeSymbol\s/g, '.safe_symbol'],
[ /\.safeMarket\s/g, '.safe_market'],
[ /\.roundTimeframe/g, '.round_timeframe'],
[ /\.integerDivide/g, '.integer_divide'],
[ /\.integerModulo/g, '.integer_modulo'],
@@ -144,11 +148,11 @@ class Transpiler {
[ /\.findBroadlyMatchedKey\s/g, '.find_broadly_matched_key'],
[ /\.throwBroadlyMatchedException\s/g, '.throw_broadly_matched_exception'],
[ /\.throwExactlyMatchedException\s/g, '.throw_exactly_matched_exception'],
[ /\.findTimeframe\s/g, '.find_timeframe'],
[ /errorHierarchy/g, 'error_hierarchy'],
[ /\.base16ToBinary/g, '.base16_to_binary'],
[ /\'use strict\';?\s+/g, '' ],
[ /\.urlencodeWithArrayRepeat\s/g, '.urlencode_with_array_repeat' ],
[ /\.bind\s*\(this\)([,;])$/mg, '$1' ],
[ /\.call\s*\(this, /g, '(' ]
]
}
@@ -167,6 +171,11 @@ class Transpiler {
[ /typeof\s+(.+?)\s+\=\=\=?\s+\'undefined\'/g, '$1 is None' ],
[ /typeof\s+(.+?)\s+\!\=\=?\s+\'undefined\'/g, '$1 is not None' ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'number\'/g, "isinstance($1[$2], numbers.Real)" ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'number\'/g, "isinstance($1[$2], numbers.Real)'" ],
[ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'number\'/g, "isinstance($1, numbers.Real)" ],
[ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'number\'/g, "isinstance($1, numbers.Real)" ],
[ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+undefined/g, '$1[$2] is None' ],
[ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+undefined/g, '$1[$2] is not None' ],
[ /([^\s]+)\s+\=\=\=?\s+undefined/g, '$1 is None' ],
@@ -182,11 +191,10 @@ class Transpiler {
[ /\=\=\=?/g, '==' ],
[ /\!\=\=?/g, '!=' ],
[ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
[ /this\.stringToBase64\s/g, 'base64.b64encode' ],
[ /this\.binaryToBase16\s/g, 'base64.b16encode' ],
[ /this\.base64ToBinary\s/g, 'base64.b64decode' ],
[ /\.shift\s*\(\)/g, '.pop(0)' ],
[ /Number\.MAX_SAFE_INTEGER/g, 'float(\'inf\')']
[ /Number\.MAX_SAFE_INTEGER/g, 'float(\'inf\')'],
[ /function\s*(\w+\s*\([^)]+\))\s*{/g, 'def $1:'],
[ /assert\s*\((.+)\);/g, 'assert $1'],
// insert common regexes in the middle (critical)
].concat (this.getCommonRegexes ()).concat ([
@@ -220,7 +228,6 @@ class Transpiler {
[ /\s\|\|\s/g, ' or ' ],
[ /\s\&\&\s/g, ' and ' ],
[ /\!([^\='"])/g, 'not $1'],
[ /([^\s(]+)\.length/g, 'len($1)' ],
[ /\.push\s*\(([\s\S]+?)\);/g, '.append($1);' ],
[ /^(\s*}\s*$)+/gm, '' ],
[ /\;(\s+?\/\/.+?)/g, '$1' ],
@@ -238,13 +245,14 @@ class Transpiler {
[ /self\[([^\]+]+)\]/g, 'getattr(self, $1)' ],
[ /([^\s]+)\.slice \(([^\,\)]+)\,\s?([^\)]+)\)/g, '$1[$2:$3]' ],
[ /([^\s]+)\.slice \(([^\)\:]+)\)/g, '$1[$2:]' ],
[ /([^\s(:]+)\.length/g, 'len($1)' ],
[ /Math\.floor\s*\(([^\)]+)\)/g, 'int(math.floor($1))' ],
[ /Math\.abs\s*\(([^\)]+)\)/g, 'abs($1)' ],
[ /Math\.pow\s*\(([^\)]+)\)/g, 'math.pow($1)' ],
[ /Math\.round\s*\(([^\)]+)\)/g, 'int(round($1))' ],
[ /Math\.ceil\s*\(([^\)]+)\)/g, 'int(math.ceil($1))' ],
[ /Math\.log/g, 'math.log' ],
[ /([a-zA-Z0-9_\.]*\([^\)]+\)|[^\s]+)\s*\?\s*([^\:]+)\s+\:\s*([^\n]+)/g, '$2 if $1 else $3'],
[ /([a-zA-Z0-9_\.]*\([^\)]+\)|[^\s]+)\s+\?\s*([^\:]+)\s+\:\s*([^\n]+)/g, '$2 if $1 else $3'],
[ /(^|\s)\/\//g, '$1#' ],
[ /([^\n\s]) #/g, '$1 #' ], // PEP8 E261
[ /\.indexOf/g, '.find'],
@@ -280,7 +288,8 @@ class Transpiler {
getPHPRegexes () {
return [
[ /\{([a-zA-Z0-9_]+?)\}/g, '~$1~' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
[ /\{([a-zA-Z0-9_-]+?)\}/g, '~$1~' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
[ /\!Array\.isArray\s*\(([^\)]+)\)/g, "gettype($1) === 'array' && count(array_filter(array_keys($1), 'is_string')) != 0" ],
[ /Array\.isArray\s*\(([^\)]+)\)/g, "gettype($1) === 'array' && count(array_filter(array_keys($1), 'is_string')) == 0" ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'undefined\'/g, '$1[$2] === null' ],
@@ -302,12 +311,18 @@ class Transpiler {
[ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'string\'/g, "gettype($1) === 'string'" ],
[ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'string\'/g, "gettype($1) !== 'string'" ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'number\'/g, "(is_float($1[$2]) || is_int($1[$2]))" ], // same as above but for number
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'number\'/g, "(is_float($1[$2]) || is_int($1[$2]))'" ],
[ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'number\'/g, "(is_float($1) || is_int($1))" ],
[ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'number\'/g, "(is_float($1) || is_int($1))" ],
[ /undefined/g, 'null' ],
[ /this\.extend\s/g, 'array_merge' ],
[ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
[ /this\.stringToBase64\s/g, 'base64_encode' ],
[ /this\.binaryToBase16\s/g, 'bin2hex' ],
[ /this\.base64ToBinary\s/g, 'base64_decode' ],
[ /this\.base64ToString\s/g, 'base64_decode' ],
// deepExtend is commented for PHP because it does not overwrite linear arrays
// a proper \ccxt\Exchange::deep_extend() base method is implemented instead
// [ /this\.deepExtend\s/g, 'array_replace_recursive'],
@@ -326,6 +341,7 @@ class Transpiler {
// add {}-array syntax conversions up to 20 levels deep in the same line
]).concat ([ ... Array (20) ].map (x => [ /\{([^\n\}]+)\}/g, 'array($1)' ] )).concat ([
[ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\[\s*([^\]]+)\s\]/g, '$1list($2)' ],
[ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\{\s*([^\}]+)\s\}/g, '$1array_values(list($2))' ],
[ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s/g, '$1' ],
@@ -342,13 +358,12 @@ class Transpiler {
[ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\.length\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 count($4);$5) {' ],
[ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 $4;$5) {' ],
[ /([^\s]+)\.length\;/g, 'is_array($1) ? count($1) : 0;' ],
[ /([^\s\(]+)\.length/g, 'strlen($1)' ],
[ /\.push\s*\(([\s\S]+?)\)\;/g, '[] = $1;' ],
[ /(\s)await(\s)/g, '$1' ],
[ /([\S])\: /g, '$1 => ' ],
// add {}-array syntax conversions up to 20 levels deep
]).concat ([ ... Array (20) ].map (x => [ /\{([^\;\{]+?)\}([^\s])/g, 'array($1)$2' ])).concat ([
]).concat ([ ... Array (20) ].map (x => [ /\{([^\{]+?)\}([^\s])/g, 'array($1)$2' ])).concat ([
[ /\[\s*([^\]]+?)\s*\]\.join\s*\(\s*([^\)]+?)\s*\)/g, "implode($2, array($1))" ],
@@ -361,17 +376,18 @@ class Transpiler {
// [ /\'([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "sprintf ('$1', $2)" ],
[ /([^\s]+)\.toFixed\s*\(([0-9]+)\)/g, "sprintf('%.$2f', $1)" ],
[ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "sprintf('%.' . $2 . 'f', $1)" ],
[ /parseFloat\s/g, 'floatval '],
[ /parseInt\s/g, 'intval '],
[ / \+ /g, ' . ' ],
[ / \+\= /g, ' .= ' ],
[ /parseFloat\s/g, 'floatval'],
[ /parseInt\s/g, 'intval'],
[ / \+ (?!\d)/g, ' . ' ],
[ / \+\= (?!\d)/g, ' .= ' ],
[ /([^\s\(]+(?:\s*\(.+\))?)\.toUpperCase\s*\(\)/g, 'strtoupper($1)' ],
[ /([^\s\(]+(?:\s*\(.+\))?)\.toLowerCase\s*\(\)/g, 'strtolower($1)' ],
[ /([^\s\(]+(?:\s*\(.+\))?)\.replace\s*\(([^\)]+)\)/g, 'str_replace($2, $1)' ],
[ /([^\s\(]+(?:\s*\(.+\))?)\.replace\s*\(([^)]+)\)/g, 'str_replace($2, $1)' ],
[ /this\[([^\]+]+)\]/g, '$$this->$$$1' ],
[ /([^\s\(]+).slice \(([^\)\:,]+)\)/g, 'mb_substr($1, $2)' ],
[ /([^\s\(]+).slice \(([^\,\)]+)\,\s*([^\)]+)\)/g, 'mb_substr($1, $2, $3 - $2)' ],
[ /([^\s\(]+).split \(('[^']*'|[^\,]+?)\)/g, 'explode($2, $1)' ],
[ /([^\s\(]+)\.length/g, 'strlen($1)' ],
[ /Math\.floor\s*\(([^\)]+)\)/g, '(int) floor($1)' ],
[ /Math\.abs\s*\(([^\)]+)\)/g, 'abs($1)' ],
[ /Math\.round\s*\(([^\)]+)\)/g, '(int) round($1)' ],
@@ -390,7 +406,7 @@ class Transpiler {
[ /process\.exit/g, 'exit'],
[ /super\./g, 'parent::'],
[ /\sdelete\s([^\n]+)\;/g, ' unset($1);' ],
[ /\~([a-zA-Z0-9_]+?)\~/g, '{$1}' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
[ /\~([a-zA-Z0-9_-]+?)\~/g, '{$1}' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
])
}
@@ -438,11 +454,11 @@ class Transpiler {
].join ("\n")
}
getPHPPreamble () {
getPHPPreamble (include = true) {
return [
"<?php",
"namespace ccxt;",
"include_once (__DIR__.'/../../ccxt.php');",
include ? "include_once (__DIR__.'/../../ccxt.php');" : "",
"// ----------------------------------------------------------------------------",
"",
"// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:",
@@ -513,7 +529,6 @@ class Transpiler {
createPythonClass (className, baseClass, body, methods, async = false) {
const pythonStandardLibraries = {
'base64': 'base64',
'hashlib': 'hashlib',
'math': 'math',
'json.loads': 'json',
@@ -655,6 +670,9 @@ class Transpiler {
python3Body = python3Body.replace (orderedDictRegex, '\.ordered([' + replaced + '])')
}
// snake case function names
python3Body = python3Body.replace (/def (\w+)/g, (match, group1) => 'def ' + unCamelCase (group1))
// special case for Python super
if (className) {
python3Body = python3Body.replace (/super\./g, 'super(' + className + ', self).')
@@ -702,6 +720,16 @@ class Transpiler {
allVariables.push (catchClauseMatches[1])
}
// match all variables instantiated as function parameters
let functionParamRegex = /function\s*(\w+)\s*\(([^)]+)\)/g
js = js.replace (functionParamRegex, (match, group1, group2) => 'function ' + unCamelCase (group1) + '(' + group2 + ')')
let functionParamVariables
while (functionParamVariables = functionParamRegex.exec (js)) {
const match = functionParamVariables[2]
const tokens = match.split (', ')
allVariables = allVariables.concat (tokens)
}
allVariables = allVariables.map (error => this.regexAll (error, this.getCommonRegexes ()))
// append $ to all variables in the method (PHP syntax demands $ at the beginning of a variable name)
@@ -736,10 +764,12 @@ class Transpiler {
//-----------------------------------------------------------------------------
transpilePythonAsyncToSync (oldName, newName) {
transpilePythonAsyncToSync () {
log.magenta ('Transpiling ' + oldName.yellow + ' → ' + newName.yellow)
const fileContents = fs.readFileSync (oldName, 'utf8')
const async = './python/test/test_async.py'
const sync = './python/test/test.py'
log.magenta ('Transpiling ' + async .yellow + ' → ' + sync.yellow)
const fileContents = fs.readFileSync (async, 'utf8')
let lines = fileContents.split ("\n")
lines = lines.filter (line => ![ 'import asyncio' ].includes (line))
@@ -769,8 +799,8 @@ class Transpiler {
newContents = deleteFunction ('test_tickers_async', newContents)
newContents = deleteFunction ('test_l2_order_books_async', newContents)
fs.truncateSync (newName)
fs.writeFileSync (newName, newContents)
fs.truncateSync (sync)
fs.writeFileSync (sync, newContents)
}
// ------------------------------------------------------------------------
@@ -949,38 +979,47 @@ class Transpiler {
ids = require ('../exchanges.json').ids;
} catch (e) {
}
const regex = new RegExp (pattern.replace (/[.*+?^${}()|[\]\\]/g, '\\$&'))
const classNames = fs.readdirSync (jsFolder)
.filter (file => file.includes (pattern) && (!ids || ids.includes (basename (file, pattern))))
.filter (file => file.match (regex) && (!ids || ids.includes (basename (file, '.js'))))
.map (file => this.transpileDerivedExchangeFile (jsFolder, file, options))
if (classNames.length === 0)
const classes = {}
if (classNames.length === 0) {
return null
}
let classes = {}
classNames.forEach (({ className, baseClass }) => {
classes[className] = baseClass
})
function deleteOldTranspiledFiles (folder, pattern) {
fs.readdirSync (folder)
.filter (file =>
!fs.lstatSync (folder + file).isDirectory () &&
file.match (pattern) &&
!(file.replace (/\.[a-z]+$/, '') in classes) &&
!file.match (/^Exchange|errors|__init__|\\./))
.map (file => folder + file)
.forEach (file => log.red ('Deleting ' + file.yellow) && fs.unlinkSync (file))
}
if (classNames.length > 1) {
[
[ python2Folder, /\.pyc?$/ ],
[ python3Folder, /\.pyc?$/ ],
[ phpFolder, /\.php$/ ],
].forEach (([ folder, pattern ]) => {
if (folder) {
deleteOldTranspiledFiles (folder, pattern)
function deleteOldTranspiledFiles (folder, pattern) {
fs.readdirSync (folder)
.filter (file =>
!fs.lstatSync (folder + file).isDirectory () &&
file.match (regex) &&
!(file.replace (/\.[a-z]+$/, '') in classes) &&
!file.match (/^Exchange|errors|__init__|\\./))
.map (file => folder + file)
.forEach (file => log.red ('Deleting ' + file.yellow) && fs.unlinkSync (file))
}
})
[
[ python2Folder, /\.pyc?$/ ],
[ python3Folder, /\.pyc?$/ ],
[ phpFolder, /\.php$/ ],
].forEach (([ folder, pattern ]) => {
if (folder) {
deleteOldTranspiledFiles (folder, pattern)
}
})
}
return classes
}
@@ -1065,18 +1104,32 @@ class Transpiler {
// PHP ----------------------------------------------------------------
function phpDeclareErrorClass (name, parent) {
return 'class ' + name + ' extends ' + parent + ' {};'
function phpMakeErrorClassFile (name, parent) {
const useClause = "\nuse " + parent + ";\n"
const requireClause = "\nrequire_once PATH_TO_CCXT_BASE . '" + parent + ".php';\n"
const phpBody = [
'<?php',
'',
'namespace ccxt;',
(parent === 'Exception') ? useClause : requireClause,
'class ' + name + ' extends ' + parent + ' {};',
'',
].join ("\n")
const phpFilename = './php/base/' + name + '.php'
log.bright.cyan (message, phpFilename.yellow)
fs.writeFileSync (phpFilename, phpBody)
return "require_once PATH_TO_CCXT_BASE . '" + name + ".php';"
}
const phpHeader = '<?php\n\nnamespace ccxt;\n\nuse Exception;\n\n'
const phpBaseError = 'class BaseError extends Exception {};'
const phpErrors = intellisense (root, 'BaseError', phpDeclareErrorClass)
const phpBodyIntellisense = phpHeader + phpBody + '\n\n' + phpBaseError + '\n' + phpErrors.join ('\n')
const phpErrors = intellisense (errorHierarchy, 'Exception', phpMakeErrorClassFile)
const phpBodyIntellisense = phpErrors.join ("\n") + "\n\n"
const phpFilename = './ccxt.php'
const phpFilename = './php/base/errors.php'
log.bright.cyan (message, phpFilename.yellow)
fs.writeFileSync (phpFilename, phpBodyIntellisense)
const phpRegex = /require_once PATH_TO_CCXT_BASE \. \'BaseError\.php\'\;\n(?:require_once PATH_TO_CCXT_BASE[^\n]+\n)+\n/m
replaceInFile (phpFilename, phpRegex, phpBodyIntellisense)
// TypeScript ---------------------------------------------------------
@@ -1186,6 +1239,9 @@ class Transpiler {
].join ("\n")
const phpHeader = [
"",
"include_once (__DIR__.'/fail_on_all_errors.php');",
"",
"// testDecimalToPrecisionErrorHandling",
"//",
"// $this->expectException ('ccxt\\\\BaseError');",
@@ -1292,6 +1348,81 @@ class Transpiler {
// ============================================================================
transpileExchangeTests () {
const tests = [
{
'jsFile': './js/test/Exchange/test.trade.js',
'pyFile': './python/test/test_trade.py',
'phpFile': './php/test/test_trade.php',
},
{
'jsFile': './js/test/Exchange/test.order.js',
'pyFile': './python/test/test_order.py',
'phpFile': './php/test/test_order.php',
},
{
'jsFile': './js/test/Exchange/test.transaction.js',
'pyFile': './python/test/test_transaction.py',
'phpFile': './php/test/test_transaction.php',
},
{
'jsFile': './js/test/Exchange/test.ohlcv.js',
'pyFile': './python/test/test_ohlcv.py',
'phpFile': './php/test/test_ohlcv.php',
},
]
for (const test of tests) {
this.transpileTest (test)
}
}
// ============================================================================
transpileTest (test) {
log.magenta ('Transpiling from', test.jsFile.yellow)
let js = fs.readFileSync (test.jsFile).toString ()
js = this.regexAll (js, [
[ /\'use strict\';?\s+/g, '' ],
[ /[^\n]+require[^\n]+\n/g, '' ],
[ /module.exports\s+=\s+[^;]+;/g, '' ],
])
const pythonHeader = [
'',
'import numbers # noqa: E402',
'try:',
' basestring # basestring was removed in Python 3',
'except NameError:',
' basestring = str',
'',
'',
].join('\n')
let { python3Body, python2Body, phpBody } = this.transpileJavaScriptToPythonAndPHP ({ js, removeEmptyLines: false })
const python = this.getPythonPreamble () + pythonHeader + python3Body;
const php = this.getPHPPreamble (false) + phpBody;
log.magenta ('→', test.pyFile.yellow)
log.magenta ('→', test.phpFile.yellow)
overwriteFile (test.pyFile, python)
overwriteFile (test.phpFile, php)
}
// ============================================================================
transpileTests () {
this.transpilePrecisionTests ()
this.transpileDateTimeTests ()
this.transpileCryptoTests ()
this.transpileExchangeTests ()
}
// ============================================================================
transpileEverything () {
// default pattern is '.js'
@@ -1322,11 +1453,9 @@ class Transpiler {
this.transpileErrorHierarchy ()
this.transpilePrecisionTests ()
this.transpileDateTimeTests ()
this.transpileCryptoTests ()
this.transpileTests ()
this.transpilePythonAsyncToSync ('./python/test/test_async.py', './python/test/test.py')
this.transpilePythonAsyncToSync ()
log.bright.green ('Transpiled successfully.')
}
@@ -1338,7 +1467,15 @@ class Transpiler {
if (require.main === module) { // called directly like `node module`
const transpiler = new Transpiler ()
transpiler.transpileEverything ()
const test = process.argv.includes ('--test') || process.argv.includes ('--tests')
const errors = process.argv.includes ('--error') || process.argv.includes ('--errors')
if (test) {
transpiler.transpileTests ()
} else if (errors) {
transpiler.transpileErrorHierarchy ()
} else {
transpiler.transpileEverything ()
}
} else { // if required as a module
@@ -7,9 +7,11 @@
"use strict";
const fs = require ('fs')
const log = require ('ololog')
const ansi = require ('ansicolor').nice
const { execSync } = require ('child_process')
, log = require ('ololog')
, ansi = require ('ansicolor').nice
, { execSync } = require ('child_process')
, { copyFile } = require ('./fs.js')
//-----------------------------------------------------------------------------
@@ -59,7 +61,14 @@ function vssEverything () {
vss ('./README.md', "ccxt@{version}", version)
vss ('./wiki/Install.md', "ccxt@{version}", version)
execSync ('cp ./package.json ./LICENSE.txt ./keys.json ./python/')
const pythonFiles = [
'package.json',
'LICENSE.txt',
'keys.json',
'README.md',
]
pythonFiles.forEach ((fileName) => copyFile ('./' + fileName, './python/' + fileName))
log.bright.green ('Version single-sourced successfully.')
}
@@ -0,0 +1,546 @@
declare module 'ccxt' {
/**
* Represents an associative array of a same type.
*/
interface Dictionary<T> {
[key: string]: T;
}
// errors.js -----------------------------------------
export class BaseError extends Error {
constructor(message: string);
}
export class ExchangeError extends BaseError {}
export class AuthenticationError extends ExchangeError {}
export class PermissionDenied extends AuthenticationError {}
export class AccountSuspended extends AuthenticationError {}
export class ArgumentsRequired extends ExchangeError {}
export class BadRequest extends ExchangeError {}
export class BadSymbol extends BadRequest {}
export class BadResponse extends ExchangeError {}
export class NullResponse extends BadResponse {}
export class InsufficientFunds extends ExchangeError {}
export class InvalidAddress extends ExchangeError {}
export class AddressPending extends InvalidAddress {}
export class InvalidOrder extends ExchangeError {}
export class OrderNotFound extends InvalidOrder {}
export class OrderNotCached extends InvalidOrder {}
export class CancelPending extends InvalidOrder {}
export class OrderImmediatelyFillable extends InvalidOrder {}
export class OrderNotFillable extends InvalidOrder {}
export class DuplicateOrderId extends InvalidOrder {}
export class NotSupported extends ExchangeError {}
export class NetworkError extends BaseError {}
export class DDoSProtection extends NetworkError {}
export class RateLimitExceeded extends DDoSProtection {}
export class ExchangeNotAvailable extends NetworkError {}
export class OnMaintenance extends ExchangeNotAvailable {}
export class InvalidNonce extends NetworkError {}
export class RequestTimeout extends NetworkError {}
// -----------------------------------------------
export const version: string;
export const exchanges: string[];
export interface MinMax {
min: number;
max: number | undefined;
}
export interface Market {
id: string;
symbol: string;
base: string;
quote: string;
baseId: string,
quoteId: string,
active: boolean;
precision: { base: number, quote: number, amount: number, price: number };
limits: { amount: MinMax, price: MinMax, cost?: MinMax };
tierBased: boolean,
percentage: boolean,
taker: number,
maker: number,
info: any,
}
export interface Order {
id: string;
clientOrderId: string;
datetime: string;
timestamp: number;
lastTradeTimestamp: number;
status: 'open' | 'closed' | 'canceled';
symbol: string;
type: string;
timeInForce?: string;
side: 'buy' | 'sell';
price: number;
average?: number;
amount: number;
filled: number;
remaining: number;
cost: number;
trades: Trade[];
fee: Fee;
info: any;
}
export interface OrderBook {
asks: [number, number][];
bids: [number, number][];
datetime: string;
timestamp: number;
nonce: number;
}
export interface Trade {
amount: number; // amount of base currency
datetime: string; // ISO8601 datetime with milliseconds;
id: string; // string trade id
info: any; // the original decoded JSON as is
order?: string; // string order id or undefined/None/null
price: number; // float price in quote currency
timestamp: number; // Unix timestamp in milliseconds
type?: string; // order type, 'market', 'limit', ... or undefined/None/null
side: 'buy' | 'sell'; // direction of the trade, 'buy' or 'sell'
symbol: string; // symbol in CCXT format
takerOrMaker: 'taker' | 'maker'; // string, 'taker' or 'maker'
cost: number; // total cost (including fees), `price * amount`
fee: Fee;
}
export interface Ticker {
symbol: string;
info: any;
timestamp: number;
datetime: string;
high: number;
low: number;
bid: number;
bidVolume?: number;
ask: number;
askVolume?: number;
vwap?: number;
open?: number;
close?: number;
last?: number;
previousClose?: number;
change?: number;
percentage?: number;
average?: number;
quoteVolume?: number;
baseVolume?: number;
}
export interface Transaction {
info: any;
id: string;
txid?: string;
timestamp: number;
datetime: string;
address: string;
type: "deposit" | "withdrawal";
amount: number;
currency: string;
status: "pending" | "ok";
updated: number;
fee: Fee;
}
export interface Tickers extends Dictionary<Ticker> {
info: any;
}
export interface Currency {
id: string;
code: string;
numericId?: number;
precision: number;
}
export interface Balance {
free: number;
used: number;
total: number;
}
export interface PartialBalances extends Dictionary<number> {
}
export interface Balances extends Dictionary<Balance> {
info: any;
}
export interface DepositAddress {
currency: string;
address: string;
status: string;
info: any;
}
export interface Fee {
type: 'taker' | 'maker';
currency: string;
rate: number;
cost: number;
}
export interface WithdrawalResponse {
info: any;
id: string;
}
export interface DepositAddressResponse {
currency: string;
address: string;
info: any;
tag?: string;
}
/** [ timestamp, open, high, low, close, volume ] */
export type OHLCV = [number, number, number, number, number, number];
/** Request parameters */
type Params = Dictionary<string | number>;
export class Exchange {
constructor(config?: {[key in keyof Exchange]?: Exchange[key]});
// allow dynamic keys
[key: string]: any;
// properties
version: string;
apiKey: string;
secret: string;
password: string;
uid: string;
requiredCredentials: {
apiKey: boolean;
secret: boolean;
uid: boolean;
login: boolean;
password: boolean;
twofa: boolean;
privateKey: boolean;
walletAddress: boolean;
token: boolean;
};
options: {
[key: string]: any;
};
urls: {
logo: string;
api: string | Dictionary<string>;
test: string | Dictionary<string>;
www: string;
doc: string[];
api_management?: string;
fees: string;
referral: string;
};
precisionMode: number;
hash: any;
hmac: any;
jwt: any;
binaryConcat: any;
stringToBinary: any;
stringToBase64: any;
base64ToBinary: any;
base64ToString: any;
binaryToString: any;
utf16ToBase64: any;
urlencode: any;
pluck: any;
unique: any;
extend: any;
deepExtend: any;
flatten: any;
groupBy: any;
indexBy: any;
sortBy: any;
keysort: any;
decimal: any;
safeFloat: any;
safeString: any;
safeInteger: any;
safeValue: any;
capitalize: any;
json: JSON["stringify"]
sum: any;
ordered: any;
aggregate: any;
truncate: any;
name: string;
// nodeVersion: string;
fees: object;
enableRateLimit: boolean;
countries: string[];
// set by loadMarkets
markets: Dictionary<Market>;
marketsById: Dictionary<Market>;
currencies: Dictionary<Currency>;
ids: string[];
symbols: string[];
id: string;
proxy: string;
parse8601: typeof Date.parse
milliseconds: typeof Date.now;
rateLimit: number; // milliseconds = seconds * 1000
timeout: number; // milliseconds
verbose: boolean;
twofa: boolean;// two-factor authentication
substituteCommonCurrencyCodes: boolean;
timeframes: Dictionary<number | string>;
has: Dictionary<boolean | 'emulated'>; // https://github.com/ccxt/ccxt/pull/1984
balance: object;
orderbooks: object;
orders: object;
trades: object;
userAgent: { 'User-Agent': string } | false;
limits: { amount: MinMax, price: MinMax, cost: MinMax };
hasCancelAllOrders: boolean;
hasCancelOrder: boolean;
hasCancelOrders: boolean;
hasCORS: boolean;
hasCreateDepositAddress: boolean;
hasCreateLimitOrder: boolean;
hasCreateMarketOrder: boolean;
hasCreateOrder: boolean;
hasDeposit: boolean;
hasEditOrder: boolean;
hasFetchBalance: boolean;
hasFetchBidsAsks: boolean;
hasFetchClosedOrders: boolean;
hasFetchCurrencies: boolean;
hasFetchDepositAddress: boolean;
hasFetchDeposits: boolean;
hasFetchFundingFees: boolean;
hasFetchL2OrderBook: boolean;
hasFetchLedger: boolean;
hasFetchMarkets: boolean;
hasFetchMyTrades: boolean;
hasFetchOHLCV: boolean;
hasFetchOpenOrders: boolean;
hasFetchOrder: boolean;
hasFetchOrderBook: boolean;
hasFetchOrderBooks: boolean;
hasFetchOrders: boolean;
hasFetchStatus: boolean;
hasFetchTicker: boolean;
hasFetchTickers: boolean;
hasFetchTime: boolean;
hasFetchTrades: boolean;
hasFetchTradingFee: boolean;
hasFetchTradingFees: boolean;
hasFetchTradingLimits: boolean;
hasFetchTransactions: boolean;
hasFetchWithdrawals: boolean;
hasPrivateAPI: boolean;
hasPublicAPI: boolean;
hasWithdraw: boolean;
// methods
account (): Balance;
cancelAllOrders (...args: any): Promise<any>; // TODO: add function signatures
cancelOrder (id: string, symbol?: string, params?: Params): Promise<Order>;
cancelOrders (...args: any): Promise<any>; // TODO: add function signatures
checkRequiredCredentials (): void;
commonCurrencyCode (currency: string): string;
createDepositAddress (currency: string, params?: Params): Promise<DepositAddressResponse>;
createLimitOrder (symbol: string, side: Order['side'], amount: number, price?: number, params?: Params): Promise<Order>;
createMarketOrder (symbol: string, side: Order['side'], amount: number, price?: number, params?: Params): Promise<Order>;
createOrder (symbol: string, type: Order['type'], side: Order['side'], amount: number, price?: number, params?: Params): Promise<Order>;
decode (str: string): string;
defaults (): any;
defineRestApi (api: any, methodName: any, options?: Dictionary<any>): void;
deposit (...args: any): Promise<any>; // TODO: add function signatures
describe (): any;
editOrder (id: string, symbol: string, type: Order['type'], side: Order['side'], amount: number, price?: number, params?: Params): Promise<Order>;
encode (str: string): string;
encodeURIComponent (...args: any[]): string;
extractParams (str: string): string[];
fetch (url: string, method?: string, headers?: any, body?: any): Promise<any>;
fetch2 (path: any, api?: string, method?: string, params?: Params, headers?: any, body?: any): Promise<any>;
fetchBalance (params?: Params): Promise<Balances>;
fetchBidsAsks (symbols?: string[], params?: Params): Promise<any>;
fetchClosedOrders (symbol?: string, since?: number, limit?: number, params?: Params): Promise<Order[]>;
fetchCurrencies (params?: Params): Promise<Dictionary<Currency>>;
fetchDepositAddress (currency: string, params?: Params): Promise<DepositAddressResponse>;
fetchDeposits (currency?: string, since?: number, limit?: number, params?: Params): Promise<Transaction[]>;
fetchFreeBalance (params?: Params): Promise<PartialBalances>;
fetchFundingFees (...args: any): Promise<any>; // TODO: add function signatures
fetchL2OrderBook (...args: any): Promise<any>; // TODO: add function signatures
fetchLedger (...args: any): Promise<any>; // TODO: add function signatures
fetchMarkets (): Promise<Market[]>;
fetchMyTrades (symbol?: string, since?: any, limit?: any, params?: Params): Promise<Trade[]>;
fetchOHLCV (symbol: string, timeframe?: string, since?: number, limit?: number, params?: Params): Promise<OHLCV[]>;
fetchOpenOrders (symbol?: string, since?: number, limit?: number, params?: Params): Promise<Order[]>;
fetchOrder (id: string, symbol: string, params?: Params): Promise<Order>;
fetchOrderBook (symbol: string, limit?: number, params?: Params): Promise<OrderBook>;
fetchOrderBooks (...args: any): Promise<any>; // TODO: add function signatures
fetchOrders (symbol?: string, since?: number, limit?: number, params?: Params): Promise<Order[]>;
fetchOrderStatus (id: string, market: string): Promise<string>;
fetchStatus (...args: any): Promise<any>; // TODO: add function signatures
fetchTicker (symbol: string, params?: Params): Promise<Ticker>;
fetchTickers (symbols?: string[], params?: Params): Promise<Dictionary<Ticker>>;
fetchTime (params?: Params): Promise<number>;
fetchTotalBalance (params?: Params): Promise<PartialBalances>;
fetchTrades (symbol: string, since?: number, limit?: number, params?: Params): Promise<Trade[]>;
fetchTradingFee (...args: any): Promise<any>; // TODO: add function signatures
fetchTradingFees (...args: any): Promise<any>; // TODO: add function signatures
fetchTradingLimits (...args: any): Promise<any>; // TODO: add function signatures
fetchTransactions (currency?: string, since?: number, limit?: number, params?: Params): Promise<Transaction[]>;
fetchUsedBalance (params?: Params): Promise<PartialBalances>;
fetchWithdrawals (currency?: string, since?: number, limit?: number, params?: Params): Promise<Transaction[]>;
getMarket (symbol: string): Market;
initRestRateLimiter (): void;
iso8601 (timestamp: number | string): string;
loadMarkets (reload?: boolean): Promise<Dictionary<Market>>;
market (symbol: string): Market;
marketId (symbol: string): string;
marketIds (symbols: string[]): string[];
microseconds (): number;
nonce (): number;
parseTimeframe (timeframe: string): number;
purgeCachedOrders (timestamp: number): void;
request (path: string, api?: string, method?: string, params?: Params, headers?: any, body?: any): Promise<any>;
seconds (): number;
setMarkets (markets: Market[], currencies?: Currency[]): Dictionary<Market>;
symbol (symbol: string): string;
withdraw (currency: string, amount: number, address: string, tag?: string, params?: Params): Promise<WithdrawalResponse>;
YmdHMS (timestamp: string, infix: string) : string;
}
/* tslint:disable */
export class acx extends Exchange {}
export class aofex extends Exchange {}
export class bcex extends Exchange {}
export class bequant extends hitbtc {}
export class bibox extends Exchange {}
export class bigone extends Exchange {}
export class binance extends Exchange {}
export class binanceus extends binance {}
export class bit2c extends Exchange {}
export class bitbank extends Exchange {}
export class bitbay extends Exchange {}
export class bitcoincom extends hitbtc {}
export class bitfinex extends Exchange {}
export class bitfinex2 extends bitfinex {}
export class bitflyer extends Exchange {}
export class bitforex extends Exchange {}
export class bitget extends Exchange {}
export class bithumb extends Exchange {}
export class bitkk extends zb {}
export class bitmart extends Exchange {}
export class bitmax extends Exchange {}
export class bitmex extends Exchange {}
export class bitpanda extends Exchange {}
export class bitso extends Exchange {}
export class bitstamp extends Exchange {}
export class bitstamp1 extends Exchange {}
export class bittrex extends Exchange {}
export class bitvavo extends Exchange {}
export class bitz extends Exchange {}
export class bl3p extends Exchange {}
export class bleutrade extends Exchange {}
export class braziliex extends Exchange {}
export class btcalpha extends Exchange {}
export class btcbox extends Exchange {}
export class btcmarkets extends Exchange {}
export class btctradeua extends Exchange {}
export class btcturk extends Exchange {}
export class buda extends Exchange {}
export class bw extends Exchange {}
export class bybit extends Exchange {}
export class bytetrade extends Exchange {}
export class cex extends Exchange {}
export class chilebit extends foxbit {}
export class coinbase extends Exchange {}
export class coinbaseprime extends coinbasepro {}
export class coinbasepro extends Exchange {}
export class coincheck extends Exchange {}
export class coinegg extends Exchange {}
export class coinex extends Exchange {}
export class coinfalcon extends Exchange {}
export class coinfloor extends Exchange {}
export class coingi extends Exchange {}
export class coinmarketcap extends Exchange {}
export class coinmate extends Exchange {}
export class coinone extends Exchange {}
export class coinspot extends Exchange {}
export class crex24 extends Exchange {}
export class currencycom extends Exchange {}
export class deribit extends Exchange {}
export class digifinex extends Exchange {}
export class dsx extends Exchange {}
export class eterbase extends Exchange {}
export class exmo extends Exchange {}
export class exx extends Exchange {}
export class fcoin extends Exchange {}
export class fcoinjp extends fcoin {}
export class flowbtc extends Exchange {}
export class foxbit extends Exchange {}
export class ftx extends Exchange {}
export class gateio extends Exchange {}
export class gemini extends Exchange {}
export class hbtc extends Exchange {}
export class hitbtc extends Exchange {}
export class hollaex extends Exchange {}
export class huobijp extends huobipro {}
export class huobipro extends Exchange {}
export class huobiru extends huobipro {}
export class ice3x extends Exchange {}
export class idex extends Exchange {}
export class independentreserve extends Exchange {}
export class indodax extends Exchange {}
export class itbit extends Exchange {}
export class kraken extends Exchange {}
export class kucoin extends Exchange {}
export class kuna extends acx {}
export class lakebtc extends Exchange {}
export class latoken extends Exchange {}
export class lbank extends Exchange {}
export class liquid extends Exchange {}
export class livecoin extends Exchange {}
export class luno extends Exchange {}
export class lykke extends Exchange {}
export class mercado extends Exchange {}
export class mixcoins extends Exchange {}
export class novadax extends Exchange {}
export class oceanex extends Exchange {}
export class okcoin extends okex {}
export class okex extends Exchange {}
export class paymium extends Exchange {}
export class phemex extends Exchange {}
export class poloniex extends Exchange {}
export class probit extends Exchange {}
export class qtrade extends Exchange {}
export class rightbtc extends Exchange {}
export class ripio extends Exchange {}
export class southxchange extends Exchange {}
export class stex extends Exchange {}
export class surbitcoin extends foxbit {}
export class therock extends Exchange {}
export class tidebit extends Exchange {}
export class tidex extends Exchange {}
export class timex extends Exchange {}
export class upbit extends Exchange {}
export class vaultoro extends Exchange {}
export class vbtc extends foxbit {}
export class vcc extends Exchange {}
export class wavesexchange extends Exchange {}
export class whitebit extends Exchange {}
export class xbtce extends Exchange {}
export class xena extends Exchange {}
export class yobit extends Exchange {}
export class zaif extends Exchange {}
export class zb extends Exchange {}
/* tslint:enable */
}
@@ -35,40 +35,41 @@ const Exchange = require ('./js/base/Exchange')
//-----------------------------------------------------------------------------
// this is updated by vss.js when building
const version = '1.27.45'
const version = '1.38.75'
Exchange.ccxtVersion = version
//-----------------------------------------------------------------------------
const exchanges = {
'_1btcxe': require ('./js/_1btcxe.js'),
'acx': require ('./js/acx.js'),
'anxpro': require ('./js/anxpro.js'),
'aofex': require ('./js/aofex.js'),
'bcex': require ('./js/bcex.js'),
'bequant': require ('./js/bequant.js'),
'bibox': require ('./js/bibox.js'),
'bigone': require ('./js/bigone.js'),
'binance': require ('./js/binance.js'),
'binanceje': require ('./js/binanceje.js'),
'binanceus': require ('./js/binanceus.js'),
'bit2c': require ('./js/bit2c.js'),
'bitbank': require ('./js/bitbank.js'),
'bitbay': require ('./js/bitbay.js'),
'bitcoincom': require ('./js/bitcoincom.js'),
'bitfinex': require ('./js/bitfinex.js'),
'bitfinex2': require ('./js/bitfinex2.js'),
'bitflyer': require ('./js/bitflyer.js'),
'bitforex': require ('./js/bitforex.js'),
'bitget': require ('./js/bitget.js'),
'bithumb': require ('./js/bithumb.js'),
'bitkk': require ('./js/bitkk.js'),
'bitmart': require ('./js/bitmart.js'),
'bitmax': require ('./js/bitmax.js'),
'bitmex': require ('./js/bitmex.js'),
'bitpanda': require ('./js/bitpanda.js'),
'bitso': require ('./js/bitso.js'),
'bitstamp': require ('./js/bitstamp.js'),
'bitstamp1': require ('./js/bitstamp1.js'),
'bittrex': require ('./js/bittrex.js'),
'bitvavo': require ('./js/bitvavo.js'),
'bitz': require ('./js/bitz.js'),
'bl3p': require ('./js/bl3p.js'),
'bleutrade': require ('./js/bleutrade.js'),
@@ -76,7 +77,6 @@ const exchanges = {
'btcalpha': require ('./js/btcalpha.js'),
'btcbox': require ('./js/btcbox.js'),
'btcmarkets': require ('./js/btcmarkets.js'),
'btctradeim': require ('./js/btctradeim.js'),
'btctradeua': require ('./js/btctradeua.js'),
'btcturk': require ('./js/btcturk.js'),
'buda': require ('./js/buda.js'),
@@ -98,12 +98,12 @@ const exchanges = {
'coinmate': require ('./js/coinmate.js'),
'coinone': require ('./js/coinone.js'),
'coinspot': require ('./js/coinspot.js'),
'coolcoin': require ('./js/coolcoin.js'),
'coss': require ('./js/coss.js'),
'crex24': require ('./js/crex24.js'),
'currencycom': require ('./js/currencycom.js'),
'deribit': require ('./js/deribit.js'),
'digifinex': require ('./js/digifinex.js'),
'dsx': require ('./js/dsx.js'),
'eterbase': require ('./js/eterbase.js'),
'exmo': require ('./js/exmo.js'),
'exx': require ('./js/exx.js'),
'fcoin': require ('./js/fcoin.js'),
@@ -111,12 +111,12 @@ const exchanges = {
'flowbtc': require ('./js/flowbtc.js'),
'foxbit': require ('./js/foxbit.js'),
'ftx': require ('./js/ftx.js'),
'fybse': require ('./js/fybse.js'),
'gateio': require ('./js/gateio.js'),
'gemini': require ('./js/gemini.js'),
'hbtc': require ('./js/hbtc.js'),
'hitbtc': require ('./js/hitbtc.js'),
'hollaex': require ('./js/hollaex.js'),
'huobijp': require ('./js/huobijp.js'),
'huobipro': require ('./js/huobipro.js'),
'huobiru': require ('./js/huobiru.js'),
'ice3x': require ('./js/ice3x.js'),
@@ -124,7 +124,6 @@ const exchanges = {
'independentreserve': require ('./js/independentreserve.js'),
'indodax': require ('./js/indodax.js'),
'itbit': require ('./js/itbit.js'),
'kkex': require ('./js/kkex.js'),
'kraken': require ('./js/kraken.js'),
'kucoin': require ('./js/kucoin.js'),
'kuna': require ('./js/kuna.js'),
@@ -137,27 +136,32 @@ const exchanges = {
'lykke': require ('./js/lykke.js'),
'mercado': require ('./js/mercado.js'),
'mixcoins': require ('./js/mixcoins.js'),
'novadax': require ('./js/novadax.js'),
'oceanex': require ('./js/oceanex.js'),
'okcoin': require ('./js/okcoin.js'),
'okex': require ('./js/okex.js'),
'paymium': require ('./js/paymium.js'),
'phemex': require ('./js/phemex.js'),
'poloniex': require ('./js/poloniex.js'),
'probit': require ('./js/probit.js'),
'qtrade': require ('./js/qtrade.js'),
'rightbtc': require ('./js/rightbtc.js'),
'ripio': require ('./js/ripio.js'),
'southxchange': require ('./js/southxchange.js'),
'stex': require ('./js/stex.js'),
'stronghold': require ('./js/stronghold.js'),
'surbitcoin': require ('./js/surbitcoin.js'),
'theocean': require ('./js/theocean.js'),
'therock': require ('./js/therock.js'),
'tidebit': require ('./js/tidebit.js'),
'tidex': require ('./js/tidex.js'),
'timex': require ('./js/timex.js'),
'topq': require ('./js/topq.js'),
'upbit': require ('./js/upbit.js'),
'vaultoro': require ('./js/vaultoro.js'),
'vbtc': require ('./js/vbtc.js'),
'vcc': require ('./js/vcc.js'),
'wavesexchange': require ('./js/wavesexchange.js'),
'whitebit': require ('./js/whitebit.js'),
'xbtce': require ('./js/xbtce.js'),
'xena': require ('./js/xena.js'),
'yobit': require ('./js/yobit.js'),
'zaif': require ('./js/zaif.js'),
'zb': require ('./js/zb.js'),
@@ -0,0 +1,84 @@
{
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "script"
},
"extends": ["eslint:recommended", "airbnb-base"],
"rules": {
"strict": "off",
"semi": "error",
"indent": ["error", 4],
"init-declarations": "error",
"no-undef-init": "off",
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "never"
}],
"brace-style": ["error", "1tbs"],
"multiline-comment-style": ["error", "separate-lines"],
"dot-notation": "off",
"quote-props": ["error", "always"],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"no-whitespace-before-property": "error",
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", "always"],
"no-spaced-func": "off",
"func-call-spacing": ["error", "always"],
"block-spacing": ["error", "always"],
"keyword-spacing": ["error", { "before": true, "after": true }],
"object-curly-spacing": ["error", "always", { "objectsInObjects": false }],
"object-curly-newline": ["error", { "consistent": true }],
"space-infix-ops": "error",
"space-unary-ops": "error",
"space-in-parens": "error",
"no-nested-ternary": "error",
"eqeqeq": "error",
"quotes": ["error", "single", { "avoidEscape": true }],
"no-unused-vars": ["error", { "argsIgnorePattern": "^(symbol|price|tag|since|limit|params|market|timeframe|api|path|code|currency|response|requestHeaders|requestBody|bidsKey|asksKey)" }],
"new-parens": "error",
"new-cap": ["error"],
"no-var": "error",
"prefer-const": ["error", {
"destructuring": "any",
"ignoreReadBeforeAssign": false
}],
"no-warning-comments": ["warn", { "terms": ["fixme"] }],
"padded-blocks": ["error", "never"],
"lines-between-class-members": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"padding-line-between-statements": ["error",
{ "blankLine": "never", "prev":"*", "next": "*" },
{ "blankLine": "always", "prev":"directive", "next": "*" },
{ "blankLine": "always", "prev":"*", "next": "cjs-export" }
],
"prefer-template": "off",
"curly": "error",
"no-plusplus": "off",
"no-restricted-properties": "off",
"prefer-destructuring": "off",
"class-methods-use-this": "off",
"no-param-reassign": "off",
"max-len": "off",
"no-return-await": "off",
"array-bracket-spacing": "off",
"radix": "off",
"camelcase": "off",
"no-lonely-if": "off",
"no-mixed-operators": "off",
"no-shadow": "off",
"no-useless-concat": "off",
"no-continue": "off",
"no-else-return": "off",
"no-unneeded-ternary": "off",
"operator-assignment": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
"no-await-in-loop": "off"
}
}
@@ -16,11 +16,19 @@ module.exports = class acx extends Exchange {
'rateLimit': 1000,
'version': 'v2',
'has': {
'cancelOrder': true,
'CORS': true,
'fetchTickers': true,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchOHLCV': true,
'withdraw': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'withdraw': true,
},
'timeframes': {
'1m': '1',
@@ -227,7 +235,7 @@ module.exports = class acx extends Exchange {
}
result[symbol] = this.parseTicker (response[id], market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -264,6 +272,14 @@ module.exports = class acx extends Exchange {
};
}
async fetchTime (params = {}) {
const response = await this.publicGetTimestamp (params);
//
// 1594911427
//
return response * 1000;
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
@@ -274,14 +290,14 @@ module.exports = class acx extends Exchange {
return this.parseTrades (response, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
return [
ohlcv[0] * 1000,
ohlcv[1],
ohlcv[2],
ohlcv[3],
ohlcv[4],
ohlcv[5],
this.safeTimestamp (ohlcv, 0),
this.safeFloat (ohlcv, 1),
this.safeFloat (ohlcv, 2),
this.safeFloat (ohlcv, 3),
this.safeFloat (ohlcv, 4),
this.safeFloat (ohlcv, 5),
];
}
@@ -313,13 +329,8 @@ module.exports = class acx extends Exchange {
}
parseOrder (order, market = undefined) {
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
} else {
const marketId = this.safeString (order, 'market');
symbol = this.markets_by_id[marketId]['symbol'];
}
const marketId = this.safeString (order, 'market');
const symbol = this.safeSymbol (marketId, market);
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
const status = this.parseOrderStatus (this.safeString (order, 'state'));
const type = this.safeString (order, 'type');
@@ -334,8 +345,10 @@ module.exports = class acx extends Exchange {
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': this.safeFloat (order, 'price'),
'stopPrice': undefined,
'amount': this.safeFloat (order, 'volume'),
'filled': this.safeFloat (order, 'executed_volume'),
'remaining': this.safeFloat (order, 'remaining_volume'),
@@ -227,7 +227,7 @@ module.exports = class aofex extends Exchange {
return result;
}
parseOHLCV (ohlcv, market = undefined, timeframe = '5m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// {
// id: 1584950100,
@@ -297,7 +297,7 @@ module.exports = class aofex extends Exchange {
//
const result = this.safeValue (response, 'result', {});
const data = this.safeValue (result, 'data', []);
return this.parseOHLCVs (data, market, timeframe, since, limit);
return this.parseOHLCVs (data, market, since, limit);
}
async fetchBalance (params = {}) {
@@ -422,13 +422,9 @@ module.exports = class aofex extends Exchange {
const percentage = change / open * 100;
const baseVolume = this.safeFloat (ticker, 'amount');
const quoteVolume = this.safeFloat (ticker, 'vol');
let vwap = undefined;
if (quoteVolume !== undefined) {
if (baseVolume !== undefined) {
if (baseVolume > 0) {
vwap = parseFloat (this.priceToPrecision (symbol, quoteVolume / baseVolume));
}
}
let vwap = this.vwap (baseVolume, quoteVolume);
if (vwap !== undefined) {
vwap = parseFloat (this.priceToPrecision (symbol, vwap));
}
return {
'symbol': symbol,
@@ -487,23 +483,10 @@ module.exports = class aofex extends Exchange {
const result = {};
for (let i = 0; i < tickers.length; i++) {
const marketId = this.safeString (tickers[i], 'symbol');
let market = undefined;
let symbol = marketId;
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
const market = this.safeMarket (marketId, undefined, '-');
const symbol = market['symbol'];
const data = this.safeValue (tickers[i], 'data', {});
const ticker = this.parseTicker (data, market);
ticker['symbol'] = symbol;
result[symbol] = ticker;
result[symbol] = this.parseTicker (data, market);
}
return this.filterByArray (result, 'symbol', symbols);
}
@@ -707,26 +690,12 @@ module.exports = class aofex extends Exchange {
const id = this.safeString (order, 'order_sn');
const orderStatus = this.safeString (order, 'status');
const status = this.parseOrderStatus (orderStatus);
let symbol = undefined;
const marketId = this.safeString (order, 'symbol');
let base = undefined;
let quote = undefined;
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
base = this.safeCurrencyCode (baseId);
quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
market = this.safeMarket (marketId, market, '-');
let timestamp = this.parse8601 (this.safeString (order, 'ctime'));
if (timestamp !== undefined) {
timestamp -= 28800000; // 8 hours, adjust to UTC
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
base = market['base'];
quote = market['quote'];
}
const timestamp = this.parse8601 (this.safeString (order, 'ctime')) - 28800000; // 8 hours, adjust to UTC
const orderType = this.safeString (order, 'type');
const type = (orderType === '2') ? 'limit' : 'market';
const side = this.safeString (order, 'side');
@@ -790,7 +759,7 @@ module.exports = class aofex extends Exchange {
}
}
if (feeCost !== undefined) {
const feeCurrencyCode = (side === 'buy') ? base : quote;
const feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote'];
fee = {
'cost': feeCost,
'currency': feeCurrencyCode,
@@ -826,10 +795,12 @@ module.exports = class aofex extends Exchange {
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'status': status,
'symbol': symbol,
'symbol': market['symbol'],
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'average': average,
'amount': amount,
@@ -30,7 +30,7 @@ const {
, defaultFetch
} = functions
const {
const { // eslint-disable-line object-curly-newline
ExchangeError
, BadSymbol
, InvalidAddress
@@ -41,26 +41,10 @@ const {
, ExchangeNotAvailable
, RateLimitExceeded } = require ('./errors')
const { TRUNCATE, ROUND, DECIMAL_PLACES } = functions.precisionConstants
const { TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING } = functions.precisionConstants
const BN = require ('../static_dependencies/BN/bn')
// ----------------------------------------------------------------------------
// web3 / 0x imports
let ethAbi = undefined
, ethUtil = undefined
try {
const requireFunction = require;
ethAbi = requireFunction ('ethereumjs-abi') // eslint-disable-line global-require
ethUtil = requireFunction ('ethereumjs-util') // eslint-disable-line global-require
// we prefer bignumber.js over BN.js
// BN = requireFunction ('bn.js') // eslint-disable-line global-require
} catch (e) {
// nothing
}
// ----------------------------------------------------------------------------
module.exports = class Exchange {
@@ -75,6 +59,7 @@ module.exports = class Exchange {
'certified': false,
'pro': false,
'has': {
'loadMarkets': true,
'cancelAllOrders': false,
'cancelOrder': true,
'cancelOrders': false,
@@ -115,6 +100,7 @@ module.exports = class Exchange {
'fetchWithdrawals': false,
'privateAPI': true,
'publicAPI': true,
'signIn': false,
'withdraw': false,
},
'urls': {
@@ -198,6 +184,7 @@ module.exports = class Exchange {
'BCHSV': 'BSV',
},
'precisionMode': DECIMAL_PLACES,
'paddingMode': NO_PADDING,
'limits': {
'amount': { 'min': undefined, 'max': undefined },
'price': { 'min': undefined, 'max': undefined },
@@ -259,12 +246,14 @@ module.exports = class Exchange {
this.balance = {}
this.orderbooks = {}
this.tickers = {}
this.orders = {}
this.orders = undefined
this.trades = {}
this.transactions = {}
this.ohlcvs = {}
this.myTrades = undefined
this.requiresWeb3 = false
this.requiresEddsa = false
this.precision = {}
this.enableLastJsonResponse = true
@@ -276,7 +265,9 @@ module.exports = class Exchange {
const unCamelCaseProperties = (obj = this) => {
if (obj !== null) {
for (const k of Object.getOwnPropertyNames (obj)) {
const ownPropertyNames = Object.getOwnPropertyNames (obj)
for (let i = 0; i < ownPropertyNames.length; i++) {
const k = ownPropertyNames[i]
this[unCamelCase (k)] = this[k]
}
unCamelCaseProperties (Object.getPrototypeOf (obj))
@@ -284,24 +275,33 @@ module.exports = class Exchange {
}
unCamelCaseProperties ()
// merge configs
const config = deepExtend (this.describe (), userConfig)
// merge to this
for (const [property, value] of Object.entries (config)) {
this[property] = deepExtend (this[property], value)
const configEntries = Object.entries (this.describe ()).concat (Object.entries (userConfig))
for (let i = 0; i < configEntries.length; i++) {
const [property, value] = configEntries[i]
if (value && Object.getPrototypeOf (value) === Object.prototype) {
this[property] = deepExtend (this[property], value)
} else {
this[property] = value
}
}
if (!this.httpAgent) {
this.httpAgent = defaultFetch.http ? new defaultFetch.http.Agent ({ 'keepAlive': true }) : undefined
const agentOptions = {
'keepAlive': true,
}
if (!this.httpsAgent) {
this.httpsAgent = defaultFetch.https ? new defaultFetch.https.Agent ({ 'keepAlive': true }) : undefined
if (!this.httpAgent && defaultFetch.http && isNode) {
this.httpAgent = new defaultFetch.http.Agent (agentOptions)
}
if (!this.httpsAgent && defaultFetch.https && isNode) {
this.httpsAgent = new defaultFetch.https.Agent (agentOptions)
}
// generate old metainfo interface
for (const k in this.has) {
const hasKeys = Object.keys (this.has)
for (let i = 0; i < hasKeys.length; i++) {
const k = hasKeys[i]
this['has' + capitalize (k)] = !!this.has[k] // converts 'emulated' to true
}
@@ -381,7 +381,6 @@ module.exports = class Exchange {
const params = { method, headers, body, timeout: this.timeout }
if (this.agent) {
this.agent.keepAlive = true
params['agent'] = this.agent
} else if (this.httpAgent && url.indexOf ('http://') === 0) {
params['agent'] = this.httpAgent
@@ -430,46 +429,31 @@ module.exports = class Exchange {
}
}
defineRestApi (api, methodName, options = {}) {
for (const type of Object.keys (api)) {
for (const httpMethod of Object.keys (api[type])) {
let paths = api[type][httpMethod]
for (let i = 0; i < paths.length; i++) {
let path = paths[i].trim ()
let splitPath = path.split (/[^a-zA-Z0-9]/)
let uppercaseMethod = httpMethod.toUpperCase ()
let lowercaseMethod = httpMethod.toLowerCase ()
let camelcaseMethod = this.capitalize (lowercaseMethod)
let camelcaseSuffix = splitPath.map (this.capitalize).join ('')
let underscoreSuffix = splitPath.map (x => x.trim ().toLowerCase ()).filter (x => x.length > 0).join ('_')
let camelcase = type + camelcaseMethod + this.capitalize (camelcaseSuffix)
let underscore = type + '_' + lowercaseMethod + '_' + underscoreSuffix
if ('suffixes' in options) {
if ('camelcase' in options['suffixes']) {
camelcase += options['suffixes']['camelcase']
}
if ('underscore' in options.suffixes) {
underscore += options['suffixes']['underscore']
}
}
if ('underscore_suffix' in options) {
underscore += options.underscoreSuffix;
}
if ('camelcase_suffix' in options) {
camelcase += options.camelcaseSuffix;
}
let partial = async params => this[methodName] (path, type, uppercaseMethod, params || {})
defineRestApi (api, methodName, paths = []) {
const keys = Object.keys (api)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const value = api[key]
if (Array.isArray (value)) {
const uppercaseMethod = key.toUpperCase ()
const lowercaseMethod = key.toLowerCase ()
const camelcaseMethod = this.capitalize (lowercaseMethod)
for (let k = 0; k < value.length; k++) {
const path = value[k].trim ()
const splitPath = path.split (/[^a-zA-Z0-9]/)
const camelcaseSuffix = splitPath.map (this.capitalize).join ('')
const underscoreSuffix = splitPath.map ((x) => x.trim ().toLowerCase ()).filter ((x) => x.length > 0).join ('_')
const camelcasePrefix = [ paths[0] ].concat (paths.slice (1).map (this.capitalize)).join ('')
const underscorePrefix = [ paths[0] ].concat (paths.slice (1).map ((x) => x.trim ()).filter ((x) => x.length > 0)).join ('_')
const camelcase = camelcasePrefix + camelcaseMethod + this.capitalize (camelcaseSuffix)
const underscore = underscorePrefix + '_' + lowercaseMethod + '_' + underscoreSuffix
const typeArgument = (paths.length > 1) ? paths : paths[0]
const partial = async (params) => this[methodName] (path, typeArgument, uppercaseMethod, params || {})
this[camelcase] = partial
this[underscore] = partial
}
} else {
this.defineRestApi (value, methodName, paths.concat ([ key ]))
}
}
}
@@ -478,6 +462,10 @@ module.exports = class Exchange {
console.log (... args)
}
setHeaders (headers) {
return headers;
}
fetch (url, method = 'GET', headers = undefined, body = undefined) {
if (isNode && this.userAgent) {
@@ -505,6 +493,7 @@ module.exports = class Exchange {
}
headers = extend (this.headers, headers)
headers = this.setHeaders (headers)
if (this.verbose) {
this.print ("fetch:\n", this.id, method, url, "\nRequest:\n", headers, "\n", body, "\n")
@@ -567,39 +556,11 @@ module.exports = class Exchange {
// override me
}
defaultErrorHandler (code, reason, url, method, headers, body, response) {
if ((code >= 200) && (code <= 299)) {
return
}
let details = body
handleHttpStatusCode (code, reason, url, method, body) {
const codeAsString = code.toString ()
let ErrorClass = undefined
if (codeAsString in this.httpExceptions) {
ErrorClass = this.httpExceptions[codeAsString]
}
if (response === undefined) {
const maintenance = body.match (/offline|busy|retry|wait|unavailable|maintain|maintenance|maintenancing/i)
const ddosProtection = body.match (/cloudflare|incapsula|overload|ddos/i)
if (maintenance) {
ErrorClass = ExchangeNotAvailable
details += ' offline, on maintenance, or unreachable from this location at the moment'
}
if (ddosProtection) {
ErrorClass = DDoSProtection
}
}
if (ErrorClass === ExchangeNotAvailable) {
details += ' (possible reasons: ' + [
'invalid API keys',
'bad or old nonce',
'exchange is down or offline',
'on maintenance',
'DDoS protection',
'rate-limiting',
].join (', ') + ')'
}
if (ErrorClass !== undefined) {
throw new ErrorClass ([ this.id, method, url, code, reason, details ].join (' '))
const ErrorClass = this.httpExceptions[codeAsString]
throw new ErrorClass ([ this.id, method, url, code, reason, body ].join (' '))
}
}
@@ -625,11 +586,11 @@ module.exports = class Exchange {
}
if (this.enableLastHttpResponse) {
this.last_http_response = responseBody // FIXME: for those classes that haven't switched to handleErrors yet
this.last_http_response = responseBody
}
if (this.enableLastJsonResponse) {
this.last_json_response = json // FIXME: for those classes that haven't switched to handleErrors yet
this.last_json_response = json
}
if (this.verbose) {
@@ -637,7 +598,7 @@ module.exports = class Exchange {
}
this.handleErrors (response.status, response.statusText, url, method, responseHeaders, responseBody, json, requestHeaders, requestBody)
this.defaultErrorHandler (response.status, response.statusText, url, method, responseHeaders, responseBody, json)
this.handleHttpStatusCode (response.status, response.statusText, url, method, responseBody)
return json || responseBody
})
@@ -657,16 +618,16 @@ module.exports = class Exchange {
this.currencies = deepExtend (currencies, this.currencies)
} else {
let baseCurrencies =
values.filter (market => 'base' in market)
.map (market => ({
values.filter ((market) => 'base' in market)
.map ((market) => ({
id: market.baseId || market.base,
numericId: (market.baseNumericId !== undefined) ? market.baseNumericId : undefined,
code: market.base,
precision: market.precision ? (market.precision.base || market.precision.amount) : 8,
}))
let quoteCurrencies =
values.filter (market => 'quote' in market)
.map (market => ({
values.filter ((market) => 'quote' in market)
.map ((market) => ({
id: market.quoteId || market.quote,
numericId: (market.quoteNumericId !== undefined) ? market.quoteNumericId : undefined,
code: market.quote,
@@ -678,9 +639,9 @@ module.exports = class Exchange {
this.quoteCurrencies = indexBy (quoteCurrencies, 'code')
const allCurrencies = baseCurrencies.concat (quoteCurrencies)
const groupedCurrencies = groupBy (allCurrencies, 'code')
const currencies = Object.keys (groupedCurrencies).map (code =>
groupedCurrencies[code].reduce ((previous, current) =>
((previous.precision > current.precision) ? previous : current), groupedCurrencies[code][0]))
const currencies = Object.keys (groupedCurrencies).map ((code) =>
groupedCurrencies[code].reduce ((previous, current) => // eslint-disable-line implicit-arrow-linebreak
((previous.precision > current.precision) ? previous : current), groupedCurrencies[code][0])) // eslint-disable-line implicit-arrow-linebreak
const sortedCurrencies = sortBy (flatten (currencies), 'code')
this.currencies = deepExtend (indexBy (sortedCurrencies, 'code'), this.currencies)
}
@@ -736,22 +697,24 @@ module.exports = class Exchange {
throw new NotSupported (this.id + ' fetchBidsAsks not supported yet')
}
async fetchOHLCVC (symbol, timeframe = '1m', since = undefined, limits = undefined, params = {}) {
if (!this.has['fetchTrades'])
async fetchOHLCVC (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
if (!this.has['fetchTrades']) {
throw new NotSupported (this.id + ' fetchOHLCV() not supported yet')
}
await this.loadMarkets ()
const trades = await this.fetchTrades (symbol, since, limits, params)
const ohlcvc = buildOHLCVC (trades, timeframe, since, limits)
const trades = await this.fetchTrades (symbol, since, limit, params)
const ohlcvc = buildOHLCVC (trades, timeframe, since, limit)
return ohlcvc
}
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limits = undefined, params = {}) {
if (!this.has['fetchTrades'])
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
if (!this.has['fetchTrades']) {
throw new NotSupported (this.id + ' fetchOHLCV() not supported yet')
}
await this.loadMarkets ()
const trades = await this.fetchTrades (symbol, since, limits, params)
const ohlcvc = buildOHLCVC (trades, timeframe, since, limits)
return ohlcvc.map (c => c.slice (0, -1))
const trades = await this.fetchTrades (symbol, since, limit, params)
const ohlcvc = buildOHLCVC (trades, timeframe, since, limit)
return ohlcvc.map ((c) => c.slice (0, -1))
}
parseTradingViewOHLCV (ohlcvs, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
@@ -801,16 +764,6 @@ module.exports = class Exchange {
throw new NotSupported (this.id + ' fetchTickers not supported yet')
}
purgeCachedOrders (before) {
const orders = Object
.values (this.orders)
.filter (order =>
(order.status === 'open') ||
(order.timestamp >= before))
this.orders = indexBy (orders, 'id')
return this.orders
}
fetchOrder (id, symbol = undefined, params = {}) {
throw new NotSupported (this.id + ' fetchOrder not supported yet');
}
@@ -889,8 +842,9 @@ module.exports = class Exchange {
}
commonCurrencyCode (currency) {
if (!this.substituteCommonCurrencyCodes)
if (!this.substituteCommonCurrencyCodes) {
return currency
}
return this.safeString (this.commonCurrencies, currency, currency)
}
@@ -916,22 +870,26 @@ module.exports = class Exchange {
currency (code) {
if (this.currencies === undefined)
if (this.currencies === undefined) {
throw new ExchangeError (this.id + ' currencies not loaded')
}
if ((typeof code === 'string') && (code in this.currencies))
if ((typeof code === 'string') && (code in this.currencies)) {
return this.currencies[code]
}
throw new ExchangeError (this.id + ' does not have currency code ' + code)
}
market (symbol) {
if (this.markets === undefined)
if (this.markets === undefined) {
throw new ExchangeError (this.id + ' markets not loaded')
}
if ((typeof symbol === 'string') && (symbol in this.markets))
if ((typeof symbol === 'string') && (symbol in this.markets)) {
return this.markets[symbol]
}
throw new BadSymbol (this.id + ' does not have market symbol ' + symbol)
}
@@ -942,7 +900,7 @@ module.exports = class Exchange {
}
marketIds (symbols) {
return symbols.map (symbol => this.marketId (symbol));
return symbols.map ((symbol) => this.marketId (symbol))
}
symbol (symbol) {
@@ -952,8 +910,9 @@ module.exports = class Exchange {
url (path, params = {}) {
let result = this.implodeParams (path, params);
const query = this.omit (params, this.extractParams (path))
if (Object.keys (query).length)
if (Object.keys (query).length) {
result += '?' + this.urlencode (query)
}
return result
}
@@ -964,7 +923,7 @@ module.exports = class Exchange {
}
parseBidsAsks (bidasks, priceKey = 0, amountKey = 1) {
return Object.values (bidasks || []).map (bidask => this.parseBidAsk (bidask, priceKey, amountKey))
return Object.values (bidasks || []).map ((bidask) => this.parseBidAsk (bidask, priceKey, amountKey))
}
async fetchL2OrderBook (symbol, limit = undefined, params = {}) {
@@ -987,33 +946,33 @@ module.exports = class Exchange {
parseBalance (balance) {
const currencies = Object.keys (this.omit (balance, [ 'info', 'free', 'used', 'total' ]));
const codes = Object.keys (this.omit (balance, [ 'info', 'free', 'used', 'total' ]));
balance['free'] = {}
balance['used'] = {}
balance['total'] = {}
for (const currency of currencies) {
if (balance[currency].total === undefined) {
if (balance[currency].free !== undefined && balance[currency].used !== undefined) {
balance[currency].total = this.sum (balance[currency].free, balance[currency].used)
for (let i = 0; i < codes.length; i++) {
const code = codes[i]
if (balance[code].total === undefined) {
if (balance[code].free !== undefined && balance[code].used !== undefined) {
balance[code].total = this.sum (balance[code].free, balance[code].used)
}
}
if (balance[currency].free === undefined) {
if (balance[currency].total !== undefined && balance[currency].used !== undefined) {
balance[currency].free = this.sum (balance[currency].total, -balance[currency].used)
if (balance[code].free === undefined) {
if (balance[code].total !== undefined && balance[code].used !== undefined) {
balance[code].free = this.sum (balance[code].total, -balance[code].used)
}
}
if (balance[currency].used === undefined) {
if (balance[currency].total !== undefined && balance[currency].free !== undefined) {
balance[currency].used = this.sum (balance[currency].total, -balance[currency].free)
if (balance[code].used === undefined) {
if (balance[code].total !== undefined && balance[code].free !== undefined) {
balance[code].used = this.sum (balance[code].total, -balance[code].free)
}
}
balance.free[currency] = balance[currency].free
balance.used[currency] = balance[currency].used
balance.total[currency] = balance[currency].total
balance.free[code] = balance[code].free
balance.used[code] = balance[code].used
balance.total[code] = balance[code].total
}
return balance
@@ -1089,15 +1048,13 @@ module.exports = class Exchange {
// single-pass filter for both symbol and since
if (valueIsDefined || sinceIsDefined) {
array = Object.values (array).filter ((entry) =>
array = array.filter ((entry) =>
((valueIsDefined ? (entry[field] === value) : true) &&
(sinceIsDefined ? (entry[key] >= since) : true)))
}
if (limit !== undefined && limit !== null) {
array = ((tail && !sinceIsDefined) ?
Object.values (array).slice (-limit) :
Object.values (array).slice (0, limit))
array = (tail && !sinceIsDefined) ? array.slice (-limit) : array.slice (0, limit)
}
return array
@@ -1171,36 +1128,95 @@ module.exports = class Exchange {
}
parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) {
// this code is commented out temporarily to catch for exchange-specific errors
// if (!this.isArray (orders)) {
// throw new ExchangeError (this.id + ' parseOrders expected an array in the orders argument, but got ' + typeof orders);
// }
let result = Object.values (orders).map (order => this.extend (this.parseOrder (order, market), params))
//
// the value of orders is either a dict or a list
//
// dict
//
// {
// 'id1': { ... },
// 'id2': { ... },
// 'id3': { ... },
// ...
// }
//
// list
//
// [
// { 'id': 'id1', ... },
// { 'id': 'id2', ... },
// { 'id': 'id3', ... },
// ...
// ]
//
let result = Array.isArray (orders) ?
Object.values (orders).map ((order) => this.extend (this.parseOrder (order, market), params)) :
Object.entries (orders).map (([ id, order ]) => this.extend (this.parseOrder (this.extend ({ 'id': id }, order), market), params))
result = sortBy (result, 'timestamp')
const symbol = (market !== undefined) ? market['symbol'] : undefined
return this.filterBySymbolSinceLimit (result, symbol, since, limit)
}
safeCurrency (currencyId, currency = undefined) {
if ((currencyId === undefined) && (currency !== undefined)) {
return currency
}
if ((this.currencies_by_id !== undefined) && (currencyId in this.currencies_by_id)) {
return this.currencies_by_id[currencyId]
}
return {
'id': currencyId,
'code': (currencyId === undefined) ? currencyId : this.commonCurrencyCode (currencyId.toUpperCase ()),
}
}
safeCurrencyCode (currencyId, currency = undefined) {
let code = undefined
if (currencyId !== undefined) {
if (this.currencies_by_id !== undefined && currencyId in this.currencies_by_id) {
code = this.currencies_by_id[currencyId]['code']
} else {
code = this.commonCurrencyCode (currencyId.toUpperCase ())
currency = this.safeCurrency (currencyId, currency)
return currency['code']
}
safeMarket (marketId, market = undefined, delimiter = undefined) {
if (marketId !== undefined) {
if (this.markets_by_id !== undefined && marketId in this.markets_by_id) {
market = this.markets_by_id[marketId]
} else if (delimiter !== undefined) {
const [ baseId, quoteId ] = marketId.split (delimiter)
const base = this.safeCurrencyCode (baseId)
const quote = this.safeCurrencyCode (quoteId)
const symbol = base + '/' + quote
return {
'id': marketId,
'symbol': symbol,
'base': base,
'quote': quote,
'baseId': baseId,
'quoteId': quoteId,
}
}
}
if (code === undefined && currency !== undefined) {
code = currency['code']
if (market !== undefined) {
return market
}
return code
return {
'id': marketId,
'symbol': marketId,
'base': undefined,
'quote': undefined,
'baseId': undefined,
'quoteId': undefined,
}
}
safeSymbol (marketId, market = undefined, delimiter = undefined) {
market = this.safeMarket (marketId, market, delimiter)
return market['symbol'];
}
filterBySymbol (array, symbol = undefined) {
return ((symbol !== undefined) ? array.filter (entry => entry.symbol === symbol) : array)
return ((symbol !== undefined) ? array.filter ((entry) => entry.symbol === symbol) : array)
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
return Array.isArray (ohlcv) ? ohlcv.slice (0, 6) : ohlcv
}
@@ -1215,7 +1231,7 @@ module.exports = class Exchange {
if (limit && (result.length >= limit)) {
break;
}
const ohlcv = this.parseOHLCV (ohlcvs[i], market, timeframe, since, limit)
const ohlcv = this.parseOHLCV (ohlcvs[i], market)
if (since && (ohlcv[0] < since)) {
continue
}
@@ -1237,8 +1253,9 @@ module.exports = class Exchange {
}
async editOrder (id, symbol, ...args) {
if (!this.enableRateLimit)
if (!this.enableRateLimit) {
throw new ExchangeError (this.id + ' editOrder() requires enableRateLimit = true')
}
await this.cancelOrder (id, symbol);
return this.createOrder (symbol, ...args)
}
@@ -1268,23 +1285,23 @@ module.exports = class Exchange {
}
costToPrecision (symbol, cost) {
return decimalToPrecision (cost, ROUND, this.markets[symbol].precision.price, this.precisionMode)
return decimalToPrecision (cost, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode)
}
priceToPrecision (symbol, price) {
return decimalToPrecision (price, ROUND, this.markets[symbol].precision.price, this.precisionMode)
return decimalToPrecision (price, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode)
}
amountToPrecision (symbol, amount) {
return decimalToPrecision (amount, TRUNCATE, this.markets[symbol].precision.amount, this.precisionMode)
return decimalToPrecision (amount, TRUNCATE, this.markets[symbol].precision.amount, this.precisionMode, this.paddingMode)
}
feeToPrecision (symbol, fee) {
return decimalToPrecision (fee, ROUND, this.markets[symbol].precision.price, this.precisionMode)
return decimalToPrecision (fee, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode)
}
currencyToPrecision (currency, fee) {
return decimalToPrecision (fee, ROUND, this.currencies[currency]['precision'], this.precisionMode);
return decimalToPrecision (fee, ROUND, this.currencies[currency]['precision'], this.precisionMode, this.paddingMode);
}
calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
@@ -1299,26 +1316,19 @@ module.exports = class Exchange {
}
}
// ------------------------------------------------------------------------
// web3 / 0x methods
static hasWeb3 () {
return ethUtil && ethAbi
}
checkRequiredDependencies () {
if (!Exchange.hasWeb3 ()) {
throw new ExchangeError ('Required dependencies missing: \nnpm i ethereumjs-util ethereumjs-abi --no-save');
}
return
}
soliditySha3 (array) {
// we only support address, uint256, and string solidity types
const encoded = []
for (const value of array) {
for (let i = 0; i < array.length; i++) {
const value = array[i]
if (Number.isInteger (value) || value.match (/^[0-9]+$/)) {
encoded.push (this.numberToBE (this.numberToString (value), 32))
} else {
const noPrefix = Exchange.remove0xPrefix (value)
const noPrefix = this.remove0xPrefix (value)
if (noPrefix.length === 40 && noPrefix.toLowerCase ().match (/^[0-9a-f]+$/)) { // check if it is an address
encoded.push (this.base16ToBinary (noPrefix))
} else {
@@ -1330,108 +1340,7 @@ module.exports = class Exchange {
return '0x' + this.hash (concated, 'keccak', 'hex')
}
getZeroExOrderHash (order) {
return this.soliditySha3 ([
order['exchangeContractAddress'], // address
order['maker'], // address
order['taker'], // address
order['makerTokenAddress'], // address
order['takerTokenAddress'], // address
order['feeRecipient'], // address
order['makerTokenAmount'], // uint256
order['takerTokenAmount'], // uint256
order['makerFee'], // uint256
order['takerFee'], // uint256
order['expirationUnixTimestampSec'], // uint256
order['salt'], // uint256
]);
}
getZeroExOrderHashV2 (order) {
// https://github.com/0xProject/0x-monorepo/blob/development/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
const addressPadding = '000000000000000000000000';
const header = '1901';
const domainStructHeader = '91ab3d17e3a50a9d89e63fd30b92be7f5336b03b287bb946787a83a9d62a2766f0f24618f4c4be1e62e026fb039a20ef96f4495294817d1027ffaa6d1f70e61ead7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5';
const orderSchemaHash = '770501f88a26ede5c04a20ef877969e961eb11fc13b78aaf414b633da0d4f86f';
const domainStructHash = ethAbi.soliditySHA3 (
[
'bytes',
'bytes',
'address'
],
[
Buffer.from (domainStructHeader, 'hex'),
Buffer.from (addressPadding, 'hex'),
order['exchangeAddress']
]
);
const orderStructHash = ethAbi.soliditySHA3 (
[
'bytes',
'bytes',
'address',
'bytes',
'address',
'bytes',
'address',
'bytes',
'address',
'uint256',
'uint256',
'uint256',
'uint256',
'uint256',
'uint256',
'string',
'string'
],
[
Buffer.from (orderSchemaHash, 'hex'),
Buffer.from (addressPadding, 'hex'),
order['makerAddress'],
Buffer.from (addressPadding, 'hex'),
order['takerAddress'],
Buffer.from (addressPadding, 'hex'),
order['feeRecipientAddress'],
Buffer.from (addressPadding, 'hex'),
order['senderAddress'],
order['makerAssetAmount'],
order['takerAssetAmount'],
order['makerFee'],
order['takerFee'],
order['expirationTimeSeconds'],
order['salt'],
ethUtil.keccak (Buffer.from (order['makerAssetData'].slice (2), 'hex')),
ethUtil.keccak (Buffer.from (order['takerAssetData'].slice (2), 'hex')),
]
);
return '0x' + ethUtil.keccak (Buffer.concat ([
Buffer.from (header, 'hex'),
domainStructHash,
orderStructHash
])).toString ('hex');
}
signZeroExOrderV2 (order, privateKey) {
const orderHash = this.getZeroExOrderHashV2 (order);
const signature = this.signMessage (orderHash, privateKey);
return this.extend (order, {
'orderHash': orderHash,
'signature': this.convertECSignatureToSignatureHex (signature),
})
}
convertECSignatureToSignatureHex (signature) {
// https://github.com/0xProject/0x-monorepo/blob/development/packages/order-utils/src/signature_utils.ts
let v = signature.v;
if (v !== 27 && v !== 28) {
v = v + 27;
}
return '0x' + v.toString (16) + signature['r'].slice (-64) + signature['s'].slice (-64) + '03'
}
static remove0xPrefix (hexData) {
remove0xPrefix (hexData) {
if (hexData.slice (0, 2) === '0x') {
return hexData.slice (2)
} else {
@@ -1441,7 +1350,7 @@ module.exports = class Exchange {
hashMessage (message) {
// takes a hex encoded message
const binaryMessage = this.base16ToBinary (Exchange.remove0xPrefix (message))
const binaryMessage = this.base16ToBinary (this.remove0xPrefix (message))
const prefix = this.stringToBinary ('\x19Ethereum Signed Message:\n' + binaryMessage.sigBytes)
return '0x' + this.hash (this.binaryConcat (prefix, binaryMessage), 'keccak', 'hex')
}
@@ -1459,6 +1368,13 @@ module.exports = class Exchange {
return this.signHash (this.hashMessage (message), privateKey.slice (-64))
}
signMessageString (message, privateKey) {
// still takes the input as a hex string
// same as above but returns a string instead of an object
const signature = this.signMessage (message, privateKey)
return signature['r'] + this.remove0xPrefix (signature['s']) + this.binaryToBase16 (this.numberToBE (signature['v']));
}
oath () {
if (typeof this.twofa !== 'undefined') {
return this.totp (this.twofa)
@@ -4,10 +4,12 @@
const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js')
const { capitalize } = require ('./string')
const { stringToBase64, utf16ToBase64, urlencodeBase64 } = require ('./encode')
const { stringToBase64, urlencodeBase64 } = require ('./encode')
const NodeRSA = require ('./../../static_dependencies/node-rsa/NodeRSA')
const { numberToLE } = require ('./encode')
const EC = require ('./../../static_dependencies/elliptic/lib/elliptic').ec
const { binaryToBase58, byteArrayToWordArray } = require ('./encode')
const elliptic = require ('./../../static_dependencies/elliptic/lib/elliptic')
const EC = elliptic.ec
const EDDSA = elliptic.eddsa
const { ArgumentsRequired } = require ('./../errors')
const BN = require ('../../static_dependencies/BN/bn.js')
@@ -96,6 +98,14 @@ function ecdsa (request, secret, algorithm = 'p256', hashFunction = undefined, f
}
}
function eddsa (request, secret, algorithm = 'ed25519') {
// used for waves.exchange (that's why the output is base58)
const curve = new EDDSA (algorithm)
const signature = curve.signModified (request, secret)
return binaryToBase58 (byteArrayToWordArray (signature.toBytes ()))
}
/* ------------------------------------------------------------------------ */
const totp = (secret) => {
@@ -142,6 +152,7 @@ module.exports = {
totp,
rsa,
ecdsa,
eddsa,
}
/* ------------------------------------------------------------------------ */
@@ -6,6 +6,14 @@ const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js')
const qs = require ('../../static_dependencies/qs/index')
const BN = require ('../../static_dependencies/BN/bn')
/* ------------------------------------------------------------------------ */
// global vars for base58 encoding
const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
let base58Decoder = null
let base58Encoder = null
/* ------------------------------------------------------------------------ */
module.exports =
@@ -19,14 +27,8 @@ module.exports =
((object[0] === '{') || (object[0] === '['))
)
, stringToBinary (str) {
const arr = new Uint8Array (str.length)
for (let i = 0; i < str.length; i++) { arr[i] = str.charCodeAt (i); }
return CryptoJS.lib.WordArray.create (arr)
}
, stringToBinary: string => CryptoJS.enc.Latin1.parse (string)
, stringToBase64: string => CryptoJS.enc.Latin1.parse (string).toString (CryptoJS.enc.Base64)
, utf16ToBase64: string => CryptoJS.enc.Utf16 .parse (string).toString (CryptoJS.enc.Base64)
, base64ToBinary: string => CryptoJS.enc.Base64.parse (string)
, base64ToString: string => CryptoJS.enc.Base64.parse (string).toString (CryptoJS.enc.Utf8)
, binaryToBase64: binary => binary.toString (CryptoJS.enc.Base64)
@@ -56,9 +58,54 @@ module.exports =
const hexArray = new BN (n).toArray ('be', padding)
return byteArrayToWordArray (hexArray)
}
, base58ToBinary: (string) => {
if (!base58Decoder) {
base58Decoder = {}
base58Encoder = {}
for (let i = 0; i < 58; i++) {
const c = base58Alphabet[i]
const bigNum = new BN (i)
base58Decoder[c] = bigNum
base58Encoder[bigNum] = c
}
}
let result = new BN (0)
const base = new BN (58)
for (let i = 0; i < string.length; i++) {
const character = string[i]
result.imul (base)
result.iadd (base58Decoder[character])
}
return byteArrayToWordArray (result.toArray ('be'))
}
, binaryToBase58: (wordArray) => {
if (!base58Encoder) {
base58Decoder = {}
base58Encoder = {}
for (let i = 0; i < 58; i++) {
const c = base58Alphabet[i]
const bigNum = new BN (i)
base58Decoder[c] = bigNum
base58Encoder[bigNum] = c
}
}
const base = new BN (58)
// hex is only compatible encoding between cryptojs and BN
const hexString = wordArray.toString (CryptoJS.enc.Hex)
let result = new BN (hexString, 16)
let string = []
while (!result.isZero ()) {
const { div, mod } = result.divmod (base)
result = div
string.push (base58Encoder[mod])
}
return string.reverse ().join ('')
}
}
function byteArrayToWordArray(ba) {
function byteArrayToWordArray (ba) {
const wa = []
for (let i = 0; i < ba.length; i++) {
wa[(i / 4) | 0] |= ba[i] << (24 - 8 * i)
@@ -0,0 +1,236 @@
"use strict";
// ----------------------------------------------------------------------------
const { isObject, isNumber, isDictionary, isArray } = require ('./type')
// ----------------------------------------------------------------------------
const keys = Object.keys
, values = (x) => ((!isArray (x)) ? Object.values (x) : x) // don't copy arrays if they're already arrays
, index = (x) => new Set (values (x))
, extend = (...args) => Object.assign ({}, ...args) // NB: side-effect free
, clone = (x) => (isArray (x) ? Array.from (x) : extend (x)) // clone arrays or objects
// ----------------------------------------------------------------------------
module.exports = {
keys
, values
, extend
, clone
, index
, ordered: (x) => x // a stub to keep assoc keys in order (in JS it does nothing, it's mostly for Python)
, unique: (x) => Array.from (index (x))
, arrayConcat: (a, b) => a.concat (b)
// ------------------------------------------------------------------------
, inArray (needle, haystack) {
return haystack.includes (needle)
}
, toArray (object) {
return Object.values (object)
}
, isEmpty (object) {
if (!object) {
return true
}
return (Array.isArray (object) ? object : Object.keys (object)).length < 1;
}
// ------------------------------------------------------------------------
, keysort (x, out = {}) {
for (const k of keys (x).sort ())
out[k] = x[k]
return out
}
// ------------------------------------------------------------------------
/*
Accepts a map/array of objects and a key name to be used as an index:
array = [
{ someKey: 'value1', anotherKey: 'anotherValue1' },
{ someKey: 'value2', anotherKey: 'anotherValue2' },
{ someKey: 'value3', anotherKey: 'anotherValue3' },
]
key = 'someKey'
Returns a map:
{
value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
value2: { someKey: 'value2', anotherKey: 'anotherValue2' },
value3: { someKey: 'value3', anotherKey: 'anotherValue3' },
}
*/
, indexBy (x, k, out = {}) {
for (const v of values (x)) {
if (k in v) {
out[v[k]] = v
}
}
return out
}
// ------------------------------------------------------------------------
/*
Accepts a map/array of objects and a key name to be used as a grouping parameter:
array = [
{ someKey: 'value1', anotherKey: 'anotherValue1' },
{ someKey: 'value1', anotherKey: 'anotherValue2' },
{ someKey: 'value3', anotherKey: 'anotherValue3' },
]
key = 'someKey'
Returns a map:
{
value1: [
{ someKey: 'value1', anotherKey: 'anotherValue1' },
{ someKey: 'value1', anotherKey: 'anotherValue2' },
]
value3: [
{ someKey: 'value3', anotherKey: 'anotherValue3' }
],
}
*/
, groupBy (x, k, out = {}) {
for (const v of values (x)) {
if (k in v) {
const p = v[k]
out[p] = out[p] || []
out[p].push (v)
}
}
return out
}
// ------------------------------------------------------------------------
/*
Accepts a map/array of objects, a key name and a key value to be used as a filter:
array = [
{ someKey: 'value1', anotherKey: 'anotherValue1' },
{ someKey: 'value2', anotherKey: 'anotherValue2' },
{ someKey: 'value3', anotherKey: 'anotherValue3' },
]
key = 'someKey'
value = 'value1'
Returns an array:
[
value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
]
*/
, filterBy (x, k, value = undefined, out = []) {
for (const v of values (x)) {
if (v[k] === value) {
out.push (v)
}
}
return out
}
// ------------------------------------------------------------------------
// NB: MUTATES ARRAY!
, sortBy: (array, key, descending = false, direction = descending ? -1 : 1) => array.sort ((a, b) => {
if (a[key] < b[key]) {
return -direction
} else if (a[key] > b[key]) {
return direction
} else {
return 0
}
})
// ------------------------------------------------------------------------
, flatten: function flatten (x, out = []) {
for (const v of x) {
if (isArray (v)) {
flatten (v, out)
} else {
out.push (v)
}
}
return out
}
// ------------------------------------------------------------------------
, pluck: (x, k) => values (x).filter ((v) => k in v).map ((v) => v[k])
// ------------------------------------------------------------------------
, omit (x, ...args) {
if (!Array.isArray (x)) {
const out = clone (x)
for (const k of args) {
if (isArray (k)) { // omit (x, ['a', 'b'])
for (const kk of k) {
delete out[kk]
}
} else {
delete out[k] // omit (x, 'a', 'b')
}
}
return out
}
return x
}
// ------------------------------------------------------------------------
, sum (...xs) {
const ns = xs.filter (isNumber) // leave only numbers
return (ns.length > 0) ? ns.reduce ((a, b) => a + b, 0) : undefined
}
// ------------------------------------------------------------------------
, deepExtend: function deepExtend (...xs) {
let out = undefined
for (const x of xs) {
if (isDictionary (x)) {
if (!isDictionary (out)) {
out = {}
}
for (const k in x) {
out[k] = deepExtend (out[k], x[k])
}
} else {
out = x
}
}
return out
}
// ----------------------------------------------------------------------------
}
@@ -1,7 +1,7 @@
'use strict';
const { ROUND_UP, ROUND_DOWN } = require ('./number')
const { NotSupported } = require ('./../errors')
const { NotSupported } = require ('../errors')
//-------------------------------------------------------------------------
// converts timeframe to seconds
@@ -48,8 +48,9 @@ const buildOHLCVC = (trades, timeframe = '1m', since = -Infinity, limit = Infini
for (let i = 0; i <= oldest; i++) {
const trade = trades[i];
if (trade.timestamp < since)
if (trade.timestamp < since) {
continue;
}
const openingTime = Math.floor (trade.timestamp / ms) * ms; // shift to the edge of m/h/d (but not M)
const candle = ohlcvs.length - 1;
@@ -100,6 +101,10 @@ const implodeParams = (string, params) => {
return string
}
function vwap (baseVolume, quoteVolume) {
return ((baseVolume !== undefined) && (quoteVolume !== undefined) && (baseVolume > 0)) ? (quoteVolume / baseVolume) : undefined
}
/* ------------------------------------------------------------------------ */
module.exports = {
@@ -115,7 +120,7 @@ module.exports = {
}
}
return Object.keys (result).map (price => [parseFloat (price), parseFloat (result[price])])
return Object.keys (result).map ((price) => [parseFloat (price), parseFloat (result[price])])
},
parseTimeframe,
@@ -125,7 +130,9 @@ module.exports = {
ROUND_DOWN,
implodeParams,
extractParams
extractParams,
vwap,
}
/* ------------------------------------------------------------------------ */
@@ -47,11 +47,12 @@ function numberToString (x) { // avoids scientific notation for too large and to
const s = x.toString ()
if (Math.abs (x) < 1.0) {
const e = parseInt (s.split ('e-')[1])
const n_e = s.split ('e-')
const n = n_e[0].replace ('.', '')
const e = parseInt (n_e[1])
const neg = (s[0] === '-')
if (e) {
x *= Math.pow (10, e - 1)
x = (neg ? '-' : '') + '0.' + (new Array (e)).join ('0') + x.toString ().substring (neg ? 3 : 2)
x = (neg ? '-' : '') + '0.' + (new Array (e)).join ('0') + n.substring (neg)
return x
}
} else {
@@ -65,7 +66,7 @@ function numberToString (x) { // avoids scientific notation for too large and to
return m[0] + m[1] + (new Array (e + 1)).join ('0')
}
}
return s;
return s
}
//-----------------------------------------------------------------------------
@@ -95,10 +96,13 @@ const decimalToPrecision = (x, roundingMode
, countingMode = DECIMAL_PLACES
, paddingMode = NO_PADDING) => {
if (numPrecisionDigits < 0) {
if (countingMode === TICK_SIZE) {
throw new Error (`TICK_SIZE cant be used with negative numPrecisionDigits`)
if (countingMode === TICK_SIZE) {
if (numPrecisionDigits <= 0) {
throw new Error ('TICK_SIZE cant be used with negative or zero numPrecisionDigits')
}
}
if (numPrecisionDigits < 0) {
const toNearest = Math.pow (10, -numPrecisionDigits)
if (roundingMode === ROUND) {
return (toNearest * decimalToPrecision (x / toNearest, roundingMode, 0, countingMode, paddingMode)).toString ()
@@ -110,7 +114,7 @@ const decimalToPrecision = (x, roundingMode
/* handle tick size */
if (countingMode === TICK_SIZE) {
const precisionDigitsString = decimalToPrecision (numPrecisionDigits, ROUND, 100, DECIMAL_PLACES, NO_PADDING)
const precisionDigitsString = decimalToPrecision (numPrecisionDigits, ROUND, 22, DECIMAL_PLACES, NO_PADDING)
const newNumPrecisionDigits = precisionFromString (precisionDigitsString)
let missing = x % numPrecisionDigits
// See: https://github.com/ccxt/ccxt/pull/6486
@@ -6,6 +6,17 @@ const now = Date.now // TODO: figure out how to utilize performance.now () prope
const microseconds = () => now () * 1000 // TODO: utilize performance.now for that purpose
const milliseconds = now
const seconds = () => Math.floor (now () / 1000)
const uuidv1 = () => {
const biasSeconds = 12219292800 // seconds from 15th Oct 1572 to Jan 1st 1970
const bias = biasSeconds * 10000000 // in hundreds of nanoseconds
const time = microseconds () * 10 + bias
const timeHex = time.toString (16)
const arranged = timeHex.slice (7, 15) + timeHex.slice (3, 7) + '1' + timeHex.slice (0, 3)
// these should be random, but we're not making more than 10 requests per microsecond so who cares
const clockId = '9696' // a 14 bit number
const macAddress = 'ff'.repeat (6)
return arranged + clockId + macAddress
}
/* ------------------------------------------------------------------------ */
@@ -115,6 +126,10 @@ const parseDate = (x) => {
return parse8601 (x);
}
const rfc2616 = (timestamp = undefined) => {
return new Date (timestamp).toUTCString ();
}
const mdy = (timestamp, infix = '-') => {
infix = infix || ''
const date = new Date (timestamp)
@@ -162,6 +177,8 @@ module.exports =
, seconds
, iso8601
, parse8601
, rfc2616
, uuidv1
, parseDate
, mdy
, ymd
@@ -6,9 +6,10 @@ const isNumber = Number.isFinite
, isInteger = Number.isInteger
, isArray = Array.isArray
, hasProps = o => ((o !== undefined) && (o !== null))
, isString = s => (typeof s === 'string')
, isString = s => (typeof s === 'string')
, isObject = o => ((o !== null) && (typeof o === 'object'))
, isDictionary = o => (isObject (o) && !isArray (o))
, isRegExp = o => (o instanceof RegExp)
, isDictionary = o => (isObject (o) && (Object.getPrototypeOf (o) === Object.prototype) && !isArray (o) && !isRegExp (o))
, isStringCoercible = x => ((hasProps (x) && x.toString) || isNumber (x))
/* ............................................. */
@@ -12,20 +12,22 @@ module.exports = class bcex extends Exchange {
return this.deepExtend (super.describe (), {
'id': 'bcex',
'name': 'BCEX',
'countries': [ 'CN', 'CA' ],
'countries': [ 'CN', 'HK' ],
'version': '1',
'has': {
'fetchBalance': true,
'fetchMarkets': true,
'createOrder': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': 'emulated',
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': false,
'fetchTrades': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchClosedOrders': 'emulated',
'fetchOpenOrders': true,
'fetchTradingLimits': true,
},
'urls': {
@@ -96,6 +98,10 @@ module.exports = class bcex extends Exchange {
'参数非法': InvalidOrder, // {'code': 1, 'msg': '参数非法'} - 'Parameter illegal'
'订单信息不存在': OrderNotFound, // {'code': 1, 'msg': '订单信息不存在'} - 'Order information does not exist'
},
'commonCurrencies': {
'UNI': 'UNI COIN',
'PNT': 'Penta',
},
'options': {
'limits': {
// hardcoding is deprecated, using these predefined values is not recommended, use loadTradingLimits instead
@@ -552,8 +558,10 @@ module.exports = class bcex extends Exchange {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'average': average,
'amount': amount,
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, AccountSuspended, ArgumentsRequired, AuthenticationError, DDoSProtection, ExchangeNotAvailable, InvalidOrder, OrderNotFound, PermissionDenied, InsufficientFunds } = require ('./base/errors');
const { ExchangeError, AccountSuspended, ArgumentsRequired, AuthenticationError, DDoSProtection, ExchangeNotAvailable, InvalidOrder, OrderNotFound, PermissionDenied, InsufficientFunds, BadSymbol } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -14,22 +14,29 @@ module.exports = class bibox extends Exchange {
'name': 'Bibox',
'countries': [ 'CN', 'US', 'KR' ],
'version': 'v1',
'hostname': 'bibox365.com',
'has': {
'cancelOrder': true,
'CORS': false,
'publicAPI': false,
'createMarketOrder': false, // or they will return https://github.com/ccxt/ccxt/issues/2338
'createOrder': true,
'fetchBalance': true,
'fetchDeposits': true,
'fetchWithdrawals': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDeposits': true,
'fetchDepositAddress': true,
'fetchFundingFees': true,
'fetchTickers': true,
'fetchOrder': true,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'createMarketOrder': false, // or they will return https://github.com/ccxt/ccxt/issues/2338
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchWithdrawals': true,
'publicAPI': false,
'withdraw': true,
},
'timeframes': {
@@ -47,13 +54,13 @@ module.exports = class bibox extends Exchange {
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/51840849/77257418-3262b000-6c85-11ea-8fb8-20bdf20b3592.jpg',
'api': 'https://api.bibox.com',
'www': 'https://www.bibox.com',
'api': 'https://api.{hostname}',
'www': 'https://www.bibox365.com',
'doc': [
'https://biboxcom.github.io/en/',
],
'fees': 'https://bibox.zendesk.com/hc/en-us/articles/360002336133',
'referral': 'https://w2.bibox.com/login/register?invite_code=05Kj3I',
'referral': 'https://w2.bibox365.com/login/register?invite_code=05Kj3I',
},
'api': {
'public': {
@@ -105,12 +112,15 @@ module.exports = class bibox extends Exchange {
'2068': InvalidOrder, // The number of orders can not be less than
'2085': InvalidOrder, // Order quantity is too small
'3012': AuthenticationError, // invalid apiKey
'3016': BadSymbol, // Trading pair error
'3024': PermissionDenied, // wrong apikey permissions
'3025': AuthenticationError, // signature failed
'4000': ExchangeNotAvailable, // current network is unstable
'4003': DDoSProtection, // server busy please try again later
},
'commonCurrencies': {
'BOX': 'DefiBox',
'BPT': 'BlockPool Token',
'KEY': 'Bihu',
'MTC': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025
'PAI': 'PCHAIN',
@@ -271,7 +281,8 @@ module.exports = class bibox extends Exchange {
};
const response = await this.publicGetMdata (this.extend (request, params));
const tickers = this.parseTickers (response['result'], symbols);
return this.indexBy (tickers, 'symbol');
const result = this.indexBy (tickers, 'symbol');
return this.filterByArray (result, 'symbol', symbols);
}
parseTrade (trade, market = undefined) {
@@ -314,7 +325,7 @@ module.exports = class bibox extends Exchange {
}
if (feeCost !== undefined) {
fee = {
'cost': feeCost,
'cost': -feeCost,
'currency': feeCurrency,
'rate': feeRate,
};
@@ -365,9 +376,19 @@ module.exports = class bibox extends Exchange {
return this.parseOrderBook (response['result'], this.safeFloat (response['result'], 'update_time'), 'bids', 'asks', 'price', 'volume');
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "time":1591448220000,
// "open":"0.02507029",
// "high":"0.02507029",
// "low":"0.02506349",
// "close":"0.02506349",
// "vol":"5.92000000"
// }
//
return [
ohlcv['time'],
this.safeInteger (ohlcv, 'time'),
this.safeFloat (ohlcv, 'open'),
this.safeFloat (ohlcv, 'high'),
this.safeFloat (ohlcv, 'low'),
@@ -386,7 +407,19 @@ module.exports = class bibox extends Exchange {
'size': limit,
};
const response = await this.publicGetMdata (this.extend (request, params));
return this.parseOHLCVs (response['result'], market, timeframe, since, limit);
//
// {
// "result":[
// {"time":1591448220000,"open":"0.02507029","high":"0.02507029","low":"0.02506349","close":"0.02506349","vol":"5.92000000"},
// {"time":1591448280000,"open":"0.02506449","high":"0.02506975","low":"0.02506108","close":"0.02506843","vol":"5.72000000"},
// {"time":1591448340000,"open":"0.02506698","high":"0.02506698","low":"0.02506452","close":"0.02506519","vol":"4.86000000"},
// ],
// "cmd":"kline",
// "ver":"1.1"
// }
//
const result = this.safeValue (response, 'result', []);
return this.parseOHLCVs (result, market, timeframe, since, limit);
}
async fetchCurrencies (params = {}) {
@@ -398,6 +431,52 @@ module.exports = class bibox extends Exchange {
'body': {},
};
const response = await this.privatePostTransfer (this.extend (request, params));
//
// {
// "result":[
// {
// "result":[
// {
// "totalBalance":"14.57582269",
// "balance":"14.57582269",
// "freeze":"0.00000000",
// "id":60,
// "symbol":"USDT",
// "icon_url":"/appimg/USDT_icon.png",
// "describe_url":"[{\"lang\":\"zh-cn\",\"link\":\"https://bibox.zendesk.com/hc/zh-cn/articles/115004798234\"},{\"lang\":\"en-ww\",\"link\":\"https://bibox.zendesk.com/hc/en-us/articles/115004798234\"}]",
// "name":"USDT",
// "enable_withdraw":1,
// "enable_deposit":1,
// "enable_transfer":1,
// "confirm_count":2,
// "is_erc20":1,
// "forbid_info":null,
// "describe_summary":"[{\"lang\":\"zh-cn\",\"text\":\"USDT 是 Tether 公司推出的基于稳定价值货币美元(USD)的代币 Tether USD(简称USDT),1USDT=1美元,用户可以随时使用 USDT 与 USD 进行1:1的兑换。\"},{\"lang\":\"en-ww\",\"text\":\"USDT is a cryptocurrency asset issued on the Bitcoin blockchain via the Omni Layer Protocol. Each USDT unit is backed by a U.S Dollar held in the reserves of the Tether Limited and can be redeemed through the Tether Platform.\"}]",
// "total_amount":4776930644,
// "supply_amount":4642367414,
// "price":"--",
// "contract_father":"OMNI",
// "supply_time":"--",
// "comment":null,
// "contract":"31",
// "original_decimals":8,
// "deposit_type":0,
// "hasCobo":0,
// "BTCValue":"0.00126358",
// "CNYValue":"100.93381445",
// "USDValue":"14.57524654",
// "children":[
// {"type":"OMNI","symbol":"USDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":2},
// {"type":"TRC20","symbol":"tUSDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":20},
// {"type":"ERC20","symbol":"eUSDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":25}
// ]
// },
// ],
// "cmd":"transfer/coinList"
// }
// ]
// }
//
const currencies = this.safeValue (response, 'result');
const result = {};
for (let i = 0; i < currencies.length; i++) {
@@ -719,8 +798,10 @@ module.exports = class bibox extends Exchange {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': cost,
'average': average,
@@ -907,7 +988,7 @@ module.exports = class bibox extends Exchange {
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = this.urls['api'] + '/' + this.version + '/' + path;
let url = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }) + '/' + this.version + '/' + path;
const cmds = this.json ([ params ]);
if (api === 'public') {
if (method !== 'GET') {
@@ -17,16 +17,23 @@ module.exports = class bigone extends Exchange {
'rateLimit': 1200, // 500 request per 10 minutes
'has': {
'cancelAllOrders': true,
'createMarketOrder': false,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchWithdrawals': true,
'withdraw': true,
},
@@ -137,6 +144,7 @@ module.exports = class bigone extends Exchange {
},
},
'commonCurrencies': {
'MBN': 'Mobilian Coin',
'ONE': 'BigONE Token',
},
});
@@ -247,24 +255,9 @@ module.exports = class bigone extends Exchange {
// "daily_change":"-0.000182"
// }
//
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (ticker, 'asset_pair_name');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const timestamp = this.milliseconds ();
const marketId = this.safeString (ticker, 'asset_pair_name');
const symbol = this.safeSymbol (marketId, market, '-');
const timestamp = undefined;
const close = this.safeFloat (ticker, 'close');
const bid = this.safeValue (ticker, 'bid', {});
const ask = this.safeValue (ticker, 'ask', {});
@@ -363,7 +356,21 @@ module.exports = class bigone extends Exchange {
const symbol = ticker['symbol'];
result[symbol] = ticker;
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTime (params = {}) {
const response = await this.publicGetPing (params);
//
// {
// "data": {
// "timestamp": 1527665262168391000
// }
// }
//
const data = this.safeValue (response, 'data', {});
const timestamp = this.safeInteger (data, 'timestamp');
return parseInt (timestamp / 1000000);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -440,20 +447,7 @@ module.exports = class bigone extends Exchange {
const price = this.safeFloat (trade, 'price');
const amount = this.safeFloat (trade, 'amount');
const marketId = this.safeString (trade, 'asset_pair_name');
let symbol = undefined;
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market, '-');
let cost = undefined;
if (amount !== undefined) {
if (price !== undefined) {
@@ -584,7 +578,7 @@ module.exports = class bigone extends Exchange {
return this.parseTrades (trades, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// {
// close: '0.021562',
@@ -645,8 +639,8 @@ module.exports = class bigone extends Exchange {
// ]
// }
//
const ohlcvs = this.safeValue (response, 'data', []);
return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
const data = this.safeValue (response, 'data', []);
return this.parseOHLCVs (data, market, timeframe, since, limit);
}
async fetchBalance (params = {}) {
@@ -692,23 +686,8 @@ module.exports = class bigone extends Exchange {
// }
//
const id = this.safeString (order, 'id');
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (order, 'asset_pair_name');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const marketId = this.safeString (order, 'asset_pair_name');
const symbol = this.safeSymbol (marketId, market, '-');
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
const price = this.safeFloat (order, 'price');
const amount = this.safeFloat (order, 'amount');
@@ -741,8 +720,10 @@ module.exports = class bigone extends Exchange {
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': undefined,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': cost,
'average': average,
@@ -758,12 +739,34 @@ module.exports = class bigone extends Exchange {
await this.loadMarkets ();
const market = this.market (symbol);
side = (side === 'buy') ? 'BID' : 'ASK';
const uppercaseType = type.toUpperCase ();
const request = {
'asset_pair_name': market['id'], // asset pair name BTC-USDT, required
'side': side, // order side one of "ASK"/"BID", required
'amount': this.amountToPrecision (symbol, amount), // order amount, string, required
'price': this.priceToPrecision (symbol, price), // order price, string, required
// 'price': this.priceToPrecision (symbol, price), // order price, string, required
'type': uppercaseType,
// 'operator': 'GTE', // stop orders only, GTE greater than and equal, LTE less than and equal
// 'immediate_or_cancel': false, // limit orders only, must be false when post_only is true
// 'post_only': false, // limit orders only, must be false when immediate_or_cancel is true
};
if (uppercaseType === 'LIMIT') {
request['price'] = this.priceToPrecision (symbol, price);
} else {
const isStopLimit = (uppercaseType === 'STOP_LIMIT');
const isStopMarket = (uppercaseType === 'STOP_MARKET');
if (isStopLimit || isStopMarket) {
const stopPrice = this.safeFloat2 (params, 'stop_price', 'stopPrice');
if (stopPrice === undefined) {
throw new ArgumentsRequired (this.id + ' createOrder requires a stop_price parameter');
}
request['stop_price'] = this.priceToPrecision (symbol, stopPrice);
params = this.omit (params, [ 'stop_price', 'stopPrice' ]);
}
if (isStopLimit) {
request['price'] = this.priceToPrecision (symbol, price);
}
}
const response = await this.privatePostOrders (this.extend (request, params));
//
// {
@@ -1071,6 +1074,24 @@ module.exports = class bigone extends Exchange {
// "txid": "0x4643bb6b393ac20a6175c713175734a72517c63d6f73a3ca90a15356f2e967da0",
// }
//
// withdraw
//
// {
// "id":1077391,
// "customer_id":1082679,
// "amount":"21.9000000000000000",
// "txid":"",
// "is_internal":false,
// "kind":"on_chain",
// "state":"PENDING",
// "inserted_at":"2020-06-03T00:50:57+00:00",
// "updated_at":"2020-06-03T00:50:57+00:00",
// "memo":"",
// "target_address":"rDYtYT3dBeuw376rvHqoZBKW3UmvguoBAf",
// "fee":"0.1000000000000000",
// "asset_symbol":"XRP"
// }
//
const currencyId = this.safeString (transaction, 'asset_symbol');
const code = this.safeCurrencyCode (currencyId);
const id = this.safeInteger (transaction, 'id');
@@ -1204,35 +1225,25 @@ module.exports = class bigone extends Exchange {
// {
// "code":0,
// "message":"",
// "data":[
// {
// "id":1,
// "customer_id":7,
// "asset_uuid":"50293b12-5be8-4f5b-b31d-d43cdd5ccc29",
// "amount":"100",
// "recipient":null,
// "state":"PENDING",
// "is_internal":true,
// "note":"asdsadsad",
// "kind":"on_chain",
// "txid":"asdasdasdsadsadsad",
// "confirms":5,
// "inserted_at":null,
// "updated_at":null,
// "completed_at":null,
// "commision":null,
// "explain":""
// }
// ]
// "data":{
// "id":1077391,
// "customer_id":1082679,
// "amount":"21.9000000000000000",
// "txid":"",
// "is_internal":false,
// "kind":"on_chain",
// "state":"PENDING",
// "inserted_at":"2020-06-03T00:50:57+00:00",
// "updated_at":"2020-06-03T00:50:57+00:00",
// "memo":"",
// "target_address":"rDYtYT3dBeuw376rvHqoZBKW3UmvguoBAf",
// "fee":"0.1000000000000000",
// "asset_symbol":"XRP"
// }
// }
//
const data = this.safeValue (response, 'data', []);
const dataLength = data.length;
if (dataLength < 1) {
throw new ExchangeError (this.id + ' withdraw() returned an empty response');
}
const transaction = data[0];
return this.parseTransaction (transaction, currency);
const data = this.safeValue (response, 'data', {});
return this.parseTransaction (data, currency);
}
handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
@@ -37,6 +37,9 @@ module.exports = class binanceus extends binance {
'maker': 0.001, // 0.1% trading fee, zero fees for all trading pairs before November 1
},
},
'options': {
'quoteOrderQty': false,
},
});
}
};
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ArgumentsRequired, ExchangeError } = require ('./base/errors');
const { ArgumentsRequired, ExchangeError, InvalidNonce, AuthenticationError, PermissionDenied } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -15,9 +15,15 @@ module.exports = class bit2c extends Exchange {
'countries': [ 'IL' ], // Israel
'rateLimit': 3000,
'has': {
'cancelOrder': true,
'CORS': false,
'fetchOpenOrders': true,
'createOrder': true,
'fetchBalance': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27766119-3593220e-5ece-11e7-8b3a-5a041f6bcc3f.jpg',
@@ -80,11 +86,18 @@ module.exports = class bit2c extends Exchange {
},
},
'options': {
'fetchTradesMethod': 'public_get_exchanges_pair_lasttrades',
'fetchTradesMethod': 'public_get_exchanges_pair_trades',
},
'exceptions': {
// { "error" : "Please provide valid APIkey" }
// { "error" : "Please provide valid nonce in Request UInt64.TryParse failed for nonce :" }
'exact': {
'Please provide valid APIkey': AuthenticationError, // { "error" : "Please provide valid APIkey" }
},
'broad': {
// { "error": "Please provide valid nonce in Request Nonce (1598218490) is not bigger than last nonce (1598218490)."}
// { "error": "Please provide valid nonce in Request UInt64.TryParse failed for nonce :" }
'Please provide valid nonce': InvalidNonce,
'please approve new terms of use on site': PermissionDenied, // { "error" : "please approve new terms of use on site." }
},
},
});
}
@@ -204,6 +217,12 @@ module.exports = class bit2c extends Exchange {
const request = {
'pair': market['id'],
};
if (since !== undefined) {
request['date'] = parseInt (since);
}
if (limit !== undefined) {
request['limit'] = limit; // max 100000
}
const response = await this[method] (this.extend (request, params));
if (typeof response === 'string') {
throw new ExchangeError (response);
@@ -286,8 +305,10 @@ module.exports = class bit2c extends Exchange {
'status': status,
'symbol': symbol,
'type': undefined,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'filled': undefined,
'remaining': undefined,
@@ -411,10 +432,27 @@ module.exports = class bit2c extends Exchange {
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'key': this.apiKey,
'sign': this.decode (signature),
'sign': signature,
};
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (response === undefined) {
return; // fallback to default error handler
}
//
// { "error" : "please approve new terms of use on site." }
// { "error": "Please provide valid nonce in Request Nonce (1598218490) is not bigger than last nonce (1598218490)."}
//
const error = this.safeString (response, 'error');
if (error !== undefined) {
const feedback = this.id + ' ' + body;
this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback);
this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback);
throw new ExchangeError (feedback); // unknown message
}
}
};
@@ -15,10 +15,17 @@ module.exports = class bitbank extends Exchange {
'countries': [ 'JP' ],
'version': 'v1',
'has': {
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchDepositAddress': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchMyTrades': true,
'fetchDepositAddress': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
'withdraw': true,
},
'timeframes': {
@@ -78,7 +85,11 @@ module.exports = class bitbank extends Exchange {
'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc' },
'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' },
'XRP/JPY': { 'id': 'xrp_jpy', 'symbol': 'XRP/JPY', 'base': 'XRP', 'quote': 'JPY', 'baseId': 'xrp', 'quoteId': 'jpy' },
'XLM/JPY': { 'id': 'xlm_jpy', 'symbol': 'XLM/JPY', 'base': 'XLM', 'quote': 'JPY', 'baseId': 'xlm', 'quoteId': 'jpy' },
'BTC/JPY': { 'id': 'btc_jpy', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy' },
'ETH/JPY': { 'id': 'eth_jpy', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy' },
'LTC/JPY': { 'id': 'ltc_jpy', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy' },
'XRP/BTC': { 'id': 'xrp_btc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc' },
},
'fees': {
'trading': {
@@ -159,7 +170,8 @@ module.exports = class bitbank extends Exchange {
'pair': market['id'],
};
const response = await this.publicGetPairTicker (this.extend (request, params));
return this.parseTicker (response['data'], market);
const data = this.safeValue (response, 'data', {});
return this.parseTicker (data, market);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -226,17 +238,29 @@ module.exports = class bitbank extends Exchange {
'pair': market['id'],
};
const response = await this.publicGetPairTransactions (this.extend (request, params));
return this.parseTrades (response['data']['transactions'], market, since, limit);
const data = this.safeValue (response, 'data', {});
const trades = this.safeValue (data, 'transactions', []);
return this.parseTrades (trades, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '5m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// "0.02501786",
// "0.02501786",
// "0.02501786",
// "0.02501786",
// "0.0000",
// 1591488000000
// ]
//
return [
ohlcv[5],
parseFloat (ohlcv[0]),
parseFloat (ohlcv[1]),
parseFloat (ohlcv[2]),
parseFloat (ohlcv[3]),
parseFloat (ohlcv[4]),
this.safeInteger (ohlcv, 5),
this.safeFloat (ohlcv, 0),
this.safeFloat (ohlcv, 1),
this.safeFloat (ohlcv, 2),
this.safeFloat (ohlcv, 3),
this.safeFloat (ohlcv, 4),
];
}
@@ -252,7 +276,28 @@ module.exports = class bitbank extends Exchange {
'yyyymmdd': date.join (''),
};
const response = await this.publicGetPairCandlestickCandletypeYyyymmdd (this.extend (request, params));
const ohlcv = this.safeValue (response['data']['candlestick'][0], 'ohlcv');
//
// {
// "success":1,
// "data":{
// "candlestick":[
// {
// "type":"5min",
// "ohlcv":[
// ["0.02501786","0.02501786","0.02501786","0.02501786","0.0000",1591488000000],
// ["0.02501747","0.02501953","0.02501747","0.02501953","0.3017",1591488300000],
// ["0.02501762","0.02501762","0.02500392","0.02500392","0.1500",1591488600000],
// ]
// }
// ],
// "timestamp":1591508668190
// }
// }
//
const data = this.safeValue (response, 'data', {});
const candlestick = this.safeValue (data, 'candlestick', []);
const first = this.safeValue (candlestick, 0, {});
const ohlcv = this.safeValue (first, 'ohlcv', []);
return this.parseOHLCVs (ohlcv, market, timeframe, since, limit);
}
@@ -260,9 +305,10 @@ module.exports = class bitbank extends Exchange {
await this.loadMarkets ();
const response = await this.privateGetUserAssets (params);
const result = { 'info': response };
const balances = response['data']['assets'];
for (let i = 0; i < balances.length; i++) {
const balance = balances[i];
const data = this.safeValue (response, 'data', {});
const assets = this.safeValue (data, 'assets', []);
for (let i = 0; i < assets.length; i++) {
const balance = assets[i];
const currencyId = this.safeString (balance, 'asset');
const code = this.safeCurrencyCode (currencyId);
const account = {
@@ -320,8 +366,10 @@ module.exports = class bitbank extends Exchange {
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'average': average,
'amount': amount,
@@ -347,10 +395,8 @@ module.exports = class bitbank extends Exchange {
'type': type,
};
const response = await this.privatePostUserSpotOrder (this.extend (request, params));
const order = this.parseOrder (response['data'], market);
const id = order['id'];
this.orders[id] = order;
return order;
const data = this.safeValue (response, 'data');
return this.parseOrder (data, market);
}
async cancelOrder (id, symbol = undefined, params = {}) {
@@ -361,7 +407,8 @@ module.exports = class bitbank extends Exchange {
'pair': market['id'],
};
const response = await this.privatePostUserSpotCancelOrder (this.extend (request, params));
return response['data'];
const data = this.safeValue (response, 'data');
return data;
}
async fetchOrder (id, symbol = undefined, params = {}) {
@@ -372,7 +419,8 @@ module.exports = class bitbank extends Exchange {
'pair': market['id'],
};
const response = await this.privateGetUserSpotOrder (this.extend (request, params));
return this.parseOrder (response['data']);
const data = this.safeValue (response, 'data');
return this.parseOrder (data, market);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -388,7 +436,9 @@ module.exports = class bitbank extends Exchange {
request['since'] = parseInt (since / 1000);
}
const response = await this.privateGetUserSpotActiveOrders (this.extend (request, params));
return this.parseOrders (response['data']['orders'], market, since, limit);
const data = this.safeValue (response, 'data', {});
const orders = this.safeValue (data, 'orders', []);
return this.parseOrders (orders, market, since, limit);
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -408,7 +458,9 @@ module.exports = class bitbank extends Exchange {
request['since'] = parseInt (since / 1000);
}
const response = await this.privateGetUserSpotTradeHistory (this.extend (request, params));
return this.parseTrades (response['data']['trades'], market, since, limit);
const data = this.safeValue (response, 'data', {});
const trades = this.safeValue (data, 'trades', []);
return this.parseTrades (trades, market, since, limit);
}
async fetchDepositAddress (code, params = {}) {
@@ -418,9 +470,11 @@ module.exports = class bitbank extends Exchange {
'asset': currency['id'],
};
const response = await this.privateGetUserWithdrawalAccount (this.extend (request, params));
const data = this.safeValue (response, 'data', {});
// Not sure about this if there could be more than one account...
const accounts = response['data']['accounts'];
const address = this.safeString (accounts[0], 'address');
const accounts = this.safeValue (data, 'accounts', []);
const firstAccount = this.safeValue (accounts, 0, {});
const address = this.safeString (firstAccount, 'address');
return {
'currency': currency,
'address': address,
@@ -440,7 +494,8 @@ module.exports = class bitbank extends Exchange {
'amount': amount,
};
const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params));
const txid = this.safeString (response['data'], 'txid');
const data = this.safeValue (response, 'data', {});
const txid = this.safeString (data, 'txid');
return {
'info': response,
'id': txid,
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { InvalidNonce, InsufficientFunds, AuthenticationError, InvalidOrder, ExchangeError, OrderNotFound, AccountSuspended, BadSymbol, OrderImmediatelyFillable, RateLimitExceeded, OnMaintenance } = require ('./base/errors');
const { InvalidNonce, InsufficientFunds, AuthenticationError, InvalidOrder, ExchangeError, OrderNotFound, AccountSuspended, BadSymbol, OrderImmediatelyFillable, RateLimitExceeded, OnMaintenance, PermissionDenied } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -15,11 +15,19 @@ module.exports = class bitbay extends Exchange {
'countries': [ 'MT', 'EU' ], // Malta
'rateLimit': 1000,
'has': {
'cancelOrder': true,
'CORS': true,
'withdraw': true,
'createOrder': true,
'fetchBalance': true,
'fetchLedger': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
'withdraw': true,
},
'timeframes': {
'1m': '60',
@@ -216,6 +224,11 @@ module.exports = class bitbay extends Exchange {
'OFFER_WOULD_HAVE_BEEN_PARTIALLY_FILLED': OrderImmediatelyFillable,
'ACTION_LIMIT_EXCEEDED': RateLimitExceeded,
'UNDER_MAINTENANCE': OnMaintenance,
'REQUEST_TIMESTAMP_TOO_OLD': InvalidNonce,
'PERMISSIONS_NOT_SUFFICIENT': PermissionDenied,
},
'commonCurrencies': {
'GGC': 'Global Game Coin',
},
});
}
@@ -328,22 +341,7 @@ module.exports = class bitbay extends Exchange {
// }
//
const marketId = this.safeString (order, 'market');
let symbol = undefined;
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if (symbol === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
}
const symbol = this.safeSymbol (marketId, market, '-');
const timestamp = this.safeInteger (order, 'time');
const amount = this.safeFloat (order, 'startAmount');
const remaining = this.safeFloat (order, 'currentAmount');
@@ -363,8 +361,10 @@ module.exports = class bitbay extends Exchange {
'status': undefined,
'symbol': symbol,
'type': this.safeString (order, 'mode'),
'timeInForce': undefined,
'side': this.safeStringLower (order, 'offerType'),
'price': this.safeFloat (order, 'rate'),
'stopPrice': undefined,
'amount': amount,
'cost': undefined,
'filled': filled,
@@ -827,25 +827,28 @@ module.exports = class bitbay extends Exchange {
return this.safeString (types, type, type);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
// [
// '1582399800000',
// {
// o: '0.0001428',
// c: '0.0001428',
// h: '0.0001428',
// l: '0.0001428',
// v: '4',
// co: '1'
// }
// ]
parseOHLCV (ohlcv, market = undefined) {
//
// [
// '1582399800000',
// {
// o: '0.0001428',
// c: '0.0001428',
// h: '0.0001428',
// l: '0.0001428',
// v: '4',
// co: '1'
// }
// ]
//
const first = this.safeValue (ohlcv, 1, {});
return [
parseInt (ohlcv[0]),
this.safeFloat (ohlcv[1], 'o'),
this.safeFloat (ohlcv[1], 'h'),
this.safeFloat (ohlcv[1], 'l'),
this.safeFloat (ohlcv[1], 'c'),
this.safeFloat (ohlcv[1], 'v'),
this.safeInteger (ohlcv, 0),
this.safeFloat (first, 'o'),
this.safeFloat (first, 'h'),
this.safeFloat (first, 'l'),
this.safeFloat (first, 'c'),
this.safeFloat (first, 'v'),
];
}
@@ -872,8 +875,18 @@ module.exports = class bitbay extends Exchange {
request['to'] = this.sum (request['from'], timerange);
}
const response = await this.v1_01PublicGetTradingCandleHistorySymbolResolution (this.extend (request, params));
const ohlcvs = this.safeValue (response, 'items', []);
return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
//
// {
// "status":"Ok",
// "items":[
// ["1591503060000",{"o":"0.02509572","c":"0.02509438","h":"0.02509664","l":"0.02509438","v":"0.02082165","co":"17"}],
// ["1591503120000",{"o":"0.02509606","c":"0.02509515","h":"0.02509606","l":"0.02509487","v":"0.04971703","co":"13"}],
// ["1591503180000",{"o":"0.02509532","c":"0.02509589","h":"0.02509589","l":"0.02509454","v":"0.01332236","co":"7"}],
// ]
// }
//
const items = this.safeValue (response, 'items', []);
return this.parseOHLCVs (items, market, timeframe, since, limit);
}
parseTrade (trade, market = undefined) {
@@ -928,33 +941,11 @@ module.exports = class bitbay extends Exchange {
}
const feeCost = this.safeFloat (trade, 'commissionValue');
const marketId = this.safeString (trade, 'market');
let base = undefined;
let quote = undefined;
let symbol = undefined;
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
base = market['base'];
quote = market['quote'];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
base = this.safeCurrencyCode (baseId);
quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if (market !== undefined) {
if (symbol === undefined) {
symbol = market['symbol'];
}
if (base === undefined) {
base = market['base'];
}
}
market = this.safeMarket (marketId, market, '-');
const symbol = market['symbol'];
let fee = undefined;
if (feeCost !== undefined) {
const feeCcy = (side === 'buy') ? base : quote;
const feeCcy = (side === 'buy') ? market['base'] : market['quote'];
fee = {
'currency': feeCcy,
'cost': feeCost,
@@ -998,7 +989,7 @@ module.exports = class bitbay extends Exchange {
}
const response = await this.v1_01PublicGetTradingTransactionsSymbol (this.extend (request, params));
const items = this.safeValue (response, 'items');
return this.parseTrades (items, symbol, since, limit);
return this.parseTrades (items, market, since, limit);
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
@@ -1013,7 +1004,9 @@ module.exports = class bitbay extends Exchange {
};
if (type === 'limit') {
request['rate'] = price;
price = parseFloat (price);
}
amount = parseFloat (amount);
const response = await this.v1_01PrivatePostTradingOfferSymbol (this.extend (request, params));
//
// unfilled (open order)
@@ -1103,8 +1096,8 @@ module.exports = class bitbay extends Exchange {
'symbol': symbol,
'type': type,
'side': side,
'price': parseFloat (price),
'amount': parseFloat (amount),
'price': price,
'amount': amount,
'cost': cost,
'filled': filled,
'remaining': remaining,
@@ -0,0 +1,34 @@
'use strict';
// ---------------------------------------------------------------------------
const hitbtc = require ('./hitbtc.js');
// ---------------------------------------------------------------------------
module.exports = class bitcoincom extends hitbtc {
describe () {
return this.deepExtend (super.describe (), {
'id': 'bitcoincom',
'name': 'bitcoin.com',
'countries': [ 'KN' ],
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/97296144-514fa300-1861-11eb-952b-3d55d492200b.jpg',
'api': {
'public': 'https://api.exchange.bitcoin.com',
'private': 'https://api.exchange.bitcoin.com',
},
'www': 'https://exchange.bitcoin.com',
'doc': 'https://api.exchange.bitcoin.com/api/2/explore',
'fees': 'https://exchange.bitcoin.com/fees-and-limits',
'referral': 'https://exchange.bitcoin.com/referral/da948b21d6c92d69',
},
'fees': {
'trading': {
'maker': 0.15 / 100,
'taker': 0.2 / 100,
},
},
});
}
};
@@ -3,8 +3,8 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { NotSupported, RateLimitExceeded, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, InvalidNonce } = require ('./base/errors');
const { SIGNIFICANT_DIGITS } = require ('./base/functions/number');
const { NotSupported, RateLimitExceeded, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, InvalidNonce, BadSymbol } = require ('./base/errors');
const { SIGNIFICANT_DIGITS, DECIMAL_PLACES, TRUNCATE, ROUND } = require ('./base/functions/number');
// ---------------------------------------------------------------------------
@@ -20,22 +20,30 @@ module.exports = class bitfinex extends Exchange {
'pro': true,
// new metainfo interface
'has': {
'CORS': false,
'cancelAllOrders': true,
'cancelOrder': true,
'CORS': false,
'createDepositAddress': true,
'createOrder': true,
'deposit': true,
'editOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchDepositAddress': true,
'fetchTradingFee': true,
'fetchTradingFees': true,
'fetchDeposits': false,
'fetchFundingFees': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchTradingFee': true,
'fetchTradingFees': true,
'fetchTransactions': true,
'fetchDeposits': false,
'fetchWithdrawals': false,
'withdraw': true,
},
@@ -289,6 +297,10 @@ module.exports = class bitfinex extends Exchange {
'DAT': 'DATA',
'DSH': 'DASH',
'DRK': 'DRK',
// https://github.com/ccxt/ccxt/issues/7399
// https://coinmarketcap.com/currencies/pnetwork/
// https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/
'EDO': 'PNT',
'GSD': 'GUSD',
'HOT': 'Hydro Protocol',
'IOS': 'IOST',
@@ -301,10 +313,12 @@ module.exports = class bitfinex extends Exchange {
'POY': 'POLY',
'QSH': 'QASH',
'QTM': 'QTUM',
'RBT': 'RBTC',
'SEE': 'SEER',
'SNG': 'SNGLS',
'SPK': 'SPANK',
'STJ': 'STORJ',
'TRI': 'TRIO',
'TSD': 'TUSD',
'YYW': 'YOYOW',
'UDC': 'USDC',
@@ -329,8 +343,10 @@ module.exports = class bitfinex extends Exchange {
'Nonce is too small.': InvalidNonce,
'No summary found.': ExchangeError, // fetchTradingFees (summary) endpoint can give this vague error message
'Cannot evaluate your available balance, please try again': ExchangeNotAvailable,
'Unknown symbol': BadSymbol,
},
'broad': {
'Invalid X-BFX-SIGNATURE': AuthenticationError,
'This API key does not have permission': PermissionDenied, // authenticated but not authorized
'not enough exchange balance for ': InsufficientFunds, // when buying cost is greater than the available quote currency
'minimum size for ': InvalidOrder, // when amount below limits.amount.min
@@ -352,9 +368,11 @@ module.exports = class bitfinex extends Exchange {
// 'BCH': 'bcash', // undocumented
'BCI': 'bci',
'BFT': 'bft',
'BSV': 'bsv',
'BTC': 'bitcoin',
'BTG': 'bgold',
'CFI': 'cfi',
'COMP': 'comp',
'DAI': 'dai',
'DADI': 'dad',
'DASH': 'dash',
@@ -373,6 +391,7 @@ module.exports = class bitfinex extends Exchange {
// https://github.com/ccxt/ccxt/issues/5833
'LEO': 'let', // ETH chain
// 'LEO': 'les', // EOS chain
'LINK': 'link',
'LRC': 'lrc',
'LTC': 'litecoin',
'LYM': 'lym',
@@ -398,6 +417,7 @@ module.exports = class bitfinex extends Exchange {
'STORJ': 'stj',
'TNB': 'tnb',
'TRX': 'trx',
'TUSD': 'tsd',
'USD': 'wire',
'USDC': 'udc', // https://github.com/ccxt/ccxt/issues/5833
'UTK': 'utk',
@@ -500,8 +520,11 @@ module.exports = class bitfinex extends Exchange {
const quote = this.safeCurrencyCode (quoteId);
const symbol = base + '/' + quote;
const precision = {
'price': market['price_precision'],
'amount': undefined,
'price': this.safeInteger (market, 'price_precision'),
// https://docs.bitfinex.com/docs/introduction#amount-precision
// The amount field allows up to 8 decimals.
// Anything exceeding this will be rounded to the 8th decimal.
'amount': 8,
};
const limits = {
'amount': {
@@ -534,7 +557,19 @@ module.exports = class bitfinex extends Exchange {
}
amountToPrecision (symbol, amount) {
return this.numberToString (amount);
// https://docs.bitfinex.com/docs/introduction#amount-precision
// The amount field allows up to 8 decimals.
// Anything exceeding this will be rounded to the 8th decimal.
return this.decimalToPrecision (amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES);
}
priceToPrecision (symbol, price) {
price = this.decimalToPrecision (price, ROUND, this.markets[symbol]['precision']['price'], this.precisionMode);
// https://docs.bitfinex.com/docs/introduction#price-precision
// The precision level of all trading prices is based on significant figures.
// All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals (e.g. 1.2345, 123.45, 1234.5, 0.00012345).
// Prices submit with a precision larger than 5 will be cut by the API.
return this.decimalToPrecision (price, TRUNCATE, 8, DECIMAL_PLACES);
}
calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
@@ -612,7 +647,7 @@ module.exports = class bitfinex extends Exchange {
const symbol = ticker['symbol'];
result[symbol] = ticker;
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -774,7 +809,7 @@ module.exports = class bitfinex extends Exchange {
async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) {
await this.loadMarkets ();
const order = {
'order_id': id,
'order_id': parseInt (id),
};
if (price !== undefined) {
order['price'] = this.priceToPrecision (symbol, price);
@@ -851,8 +886,10 @@ module.exports = class bitfinex extends Exchange {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': orderType,
'timeInForce': undefined,
'side': side,
'price': this.safeFloat (order, 'price'),
'stopPrice': undefined,
'average': this.safeFloat (order, 'avg_execution_price'),
'amount': this.safeFloat (order, 'original_amount'),
'remaining': this.safeFloat (order, 'remaining_amount'),
@@ -903,7 +940,17 @@ module.exports = class bitfinex extends Exchange {
return this.parseOrder (response);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// 1457539800000,
// 0.02594,
// 0.02594,
// 0.02594,
// 0.02594,
// 0.1
// ]
//
return [
this.safeInteger (ohlcv, 0),
this.safeFloat (ohlcv, 1),
@@ -931,6 +978,13 @@ module.exports = class bitfinex extends Exchange {
request['start'] = since;
}
const response = await this.v2GetCandlesTradeTimeframeSymbolHist (this.extend (request, params));
//
// [
// [1457539800000,0.02594,0.02594,0.02594,0.02594,0.1],
// [1457547300000,0.02577,0.02577,0.02577,0.02577,0.01],
// [1457550240000,0.0255,0.0253,0.0255,0.0252,3.2640000000000002],
// ]
//
return this.parseOHLCVs (response, market, timeframe, since, limit);
}
@@ -977,18 +1031,23 @@ module.exports = class bitfinex extends Exchange {
}
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
if (code === undefined) {
throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument');
}
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
'currency': currency['id'],
};
if (since !== undefined) {
request['since'] = parseInt (since / 1000);
let currencyId = this.safeString (params, 'currency');
const query = this.omit (params, 'currency');
let currency = undefined;
if (currencyId === undefined) {
if (code === undefined) {
throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument or a `currency` parameter');
} else {
currency = this.currency (code);
currencyId = currency['id'];
}
}
const response = await this.privatePostHistoryMovements (this.extend (request, params));
query['currency'] = currencyId;
if (since !== undefined) {
query['since'] = parseInt (since / 1000);
}
const response = await this.privatePostHistoryMovements (this.extend (query, params));
//
// [
// {
@@ -1125,6 +1184,27 @@ module.exports = class bitfinex extends Exchange {
};
}
async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.privatePostPositions (params);
//
// [
// {
// "id":943715,
// "symbol":"btcusd",
// "status":"ACTIVE",
// "base":"246.94",
// "amount":"1.0",
// "timestamp":"1444141857.0",
// "swap":"0.0",
// "pl":"-2.22042"
// }
// ]
//
// todo unify parsePosition/parsePositions
return response;
}
nonce () {
return this.milliseconds ();
}
@@ -1153,14 +1233,14 @@ module.exports = class bitfinex extends Exchange {
'request': request,
}, query);
body = this.json (query);
query = this.encode (body);
const payload = this.stringToBase64 (query);
const payload = this.stringToBase64 (body);
const secret = this.encode (this.secret);
const signature = this.hmac (payload, secret, 'sha384');
headers = {
'X-BFX-APIKEY': this.apiKey,
'X-BFX-PAYLOAD': this.decode (payload),
'X-BFX-SIGNATURE': signature,
'Content-Type': 'application/json',
};
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
@@ -20,28 +20,30 @@ module.exports = class bitfinex2 extends bitfinex {
'has': {
'CORS': false,
'cancelAllOrders': true,
'cancelOrder': true,
'createDepositAddress': true,
'createLimitOrder': true,
'createMarketOrder': true,
'createOrder': true,
'cancelOrder': true,
'deposit': false,
'editOrder': false,
'fetchDepositAddress': true,
'fetchBalance': true,
'fetchClosedOrder': true,
'fetchClosedOrders': false,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchFundingFees': false,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrder': true,
'fetchOpenOrders': true,
'fetchOrder': false,
'fetchOpenOrder': true,
'fetchClosedOrder': true,
'fetchOrderTrades': true,
'fetchStatus': true,
'fetchTickers': true,
'fetchTradingFee': false,
'fetchTradingFees': false,
'fetchTransactions': false,
'fetchTransactions': true,
'withdraw': true,
},
'timeframes': {
@@ -94,12 +96,14 @@ module.exports = class bitfinex2 extends bitfinex {
'conf/pub:map:currency:undl', // maps derivatives symbols to their underlying currency
'conf/pub:map:currency:pool', // maps symbols to underlying network/protocol they operate on
'conf/pub:map:currency:explorer', // maps symbols to their recognised block explorer URLs
'conf/pub:map:currency:tx:fee', // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745
'conf/pub:map:tx:method',
'conf/pub:list:{object}',
'conf/pub:list:{object}:{detail}',
'conf/pub:list:currency',
'conf/pub:list:pair:exchange',
'conf/pub:list:pair:margin',
'conf/pub:list:pair:futures',
'conf/pub:list:competitions',
'conf/pub:info:{object}',
'conf/pub:info:{object}:{detail}',
@@ -323,11 +327,21 @@ module.exports = class bitfinex2 extends bitfinex {
}
async fetchMarkets (params = {}) {
const response = await this.v1GetSymbolsDetails (params);
// todo drop v1 in favor of v2 configs
// pub:list:pair:exchange,pub:list:pair:margin,pub:list:pair:futures,pub:info:pair
const v2response = await this.publicGetConfPubListPairFutures (params);
const v1response = await this.v1GetSymbolsDetails (params);
const futuresMarketIds = this.safeValue (v2response, 0, []);
const result = [];
for (let i = 0; i < response.length; i++) {
const market = response[i];
for (let i = 0; i < v1response.length; i++) {
const market = v1response[i];
let id = this.safeStringUpper (market, 'pair');
let spot = true;
if (this.inArray (id, futuresMarketIds)) {
spot = false;
}
const futures = !spot;
const type = spot ? 'spot' : 'futures';
let baseId = undefined;
let quoteId = undefined;
if (id.indexOf (':') >= 0) {
@@ -346,7 +360,7 @@ module.exports = class bitfinex2 extends bitfinex {
quoteId = this.getCurrencyId (quoteId);
const precision = {
'price': this.safeInteger (market, 'price_precision'),
'amount': this.safeInteger (market, 'price_precision'),
'amount': 8, // https://github.com/ccxt/ccxt/issues/7310
};
const limits = {
'amount': {
@@ -373,14 +387,164 @@ module.exports = class bitfinex2 extends bitfinex {
'precision': precision,
'limits': limits,
'info': market,
'type': type,
'swap': false,
'spot': false,
'futures': false,
'spot': spot,
'futures': futures,
});
}
return result;
}
async fetchCurrencies (params = {}) {
const labels = [
'pub:list:currency',
'pub:map:currency:sym', // maps symbols to their API symbols, BAB > BCH
'pub:map:currency:label', // verbose friendly names, BNT > Bancor
'pub:map:currency:unit', // maps symbols to unit of measure where applicable
'pub:map:currency:undl', // maps derivatives symbols to their underlying currency
'pub:map:currency:pool', // maps symbols to underlying network/protocol they operate on
'pub:map:currency:explorer', // maps symbols to their recognised block explorer URLs
'pub:map:currency:tx:fee', // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745
];
const config = labels.join (',');
const request = {
'config': config,
};
const response = await this.publicGetConfConfig (this.extend (request, params));
//
// [
//
// a list of symbols
// ["AAA","ABS","ADA"],
//
// // sym
// // maps symbols to their API symbols, BAB > BCH
// [
// [ 'BAB', 'BCH' ],
// [ 'CNHT', 'CNHt' ],
// [ 'DSH', 'DASH' ],
// [ 'IOT', 'IOTA' ],
// [ 'LES', 'LEO-EOS' ],
// [ 'LET', 'LEO-ERC20' ],
// [ 'STJ', 'STORJ' ],
// [ 'TSD', 'TUSD' ],
// [ 'UDC', 'USDC' ],
// [ 'USK', 'USDK' ],
// [ 'UST', 'USDt' ],
// [ 'USTF0', 'USDt0' ],
// [ 'XCH', 'XCHF' ],
// [ 'YYW', 'YOYOW' ],
// // ...
// ],
// // label
// // verbose friendly names, BNT > Bancor
// [
// [ 'BAB', 'Bitcoin Cash' ],
// [ 'BCH', 'Bitcoin Cash' ],
// [ 'LEO', 'Unus Sed LEO' ],
// [ 'LES', 'Unus Sed LEO (EOS)' ],
// [ 'LET', 'Unus Sed LEO (ERC20)' ],
// // ...
// ],
// // unit
// // maps symbols to unit of measure where applicable
// [
// [ 'IOT', 'Mi|MegaIOTA' ],
// ],
// // undl
// // maps derivatives symbols to their underlying currency
// [
// [ 'USTF0', 'UST' ],
// [ 'BTCF0', 'BTC' ],
// [ 'ETHF0', 'ETH' ],
// ],
// // pool
// // maps symbols to underlying network/protocol they operate on
// [
// [ 'SAN', 'ETH' ], [ 'OMG', 'ETH' ], [ 'AVT', 'ETH' ], [ 'EDO', 'ETH' ],
// [ 'ESS', 'ETH' ], [ 'ATD', 'EOS' ], [ 'ADD', 'EOS' ], [ 'MTO', 'EOS' ],
// [ 'PNK', 'ETH' ], [ 'BAB', 'BCH' ], [ 'WLO', 'XLM' ], [ 'VLD', 'ETH' ],
// [ 'BTT', 'TRX' ], [ 'IMP', 'ETH' ], [ 'SCR', 'ETH' ], [ 'GNO', 'ETH' ],
// // ...
// ],
// // explorer
// // maps symbols to their recognised block explorer URLs
// [
// [
// 'AIO',
// [
// "https://mainnet.aion.network",
// "https://mainnet.aion.network/#/account/VAL",
// "https://mainnet.aion.network/#/transaction/VAL"
// ]
// ],
// // ...
// ],
// // fee
// // maps currencies to their withdrawal fees
// [
// ["AAA",[0,0]],
// ["ABS",[0,131.3]],
// ["ADA",[0,0.3]],
// ],
// ]
//
const indexed = {
'sym': this.indexBy (this.safeValue (response, 1, []), 0),
'label': this.indexBy (this.safeValue (response, 2, []), 0),
'unit': this.indexBy (this.safeValue (response, 3, []), 0),
'undl': this.indexBy (this.safeValue (response, 4, []), 0),
'pool': this.indexBy (this.safeValue (response, 5, []), 0),
'explorer': this.indexBy (this.safeValue (response, 6, []), 0),
'fees': this.indexBy (this.safeValue (response, 7, []), 0),
};
const ids = this.safeValue (response, 0, []);
const result = {};
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
const code = this.safeCurrencyCode (id);
const label = this.safeValue (indexed['label'], id, []);
const name = this.safeString (label, 1);
const pool = this.safeValue (indexed['pool'], id, []);
const type = this.safeString (pool, 1);
const feeValues = this.safeValue (indexed['fees'], id, []);
const fees = this.safeValue (feeValues, 1, []);
const fee = this.safeFloat (fees, 1);
const precision = 8; // default precision, todo: fix "magic constants"
id = 'f' + id;
result[code] = {
'id': id,
'code': code,
'info': [ id, label, pool, feeValues ],
'type': type,
'name': name,
'active': true,
'fee': fee,
'precision': precision,
'limits': {
'amount': {
'min': 1 / Math.pow (10, precision),
'max': undefined,
},
'price': {
'min': 1 / Math.pow (10, precision),
'max': undefined,
},
'cost': {
'min': undefined,
'max': undefined,
},
'withdraw': {
'min': fee,
'max': undefined,
},
},
};
}
return result;
}
async fetchBalance (params = {}) {
// this api call does not return the 'used' amount - use the v1 version instead (which also returns zero balances)
await this.loadMarkets ();
@@ -504,7 +668,7 @@ module.exports = class bitfinex2 extends bitfinex {
result[symbol] = this.parseTicker (ticker, market);
}
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -690,22 +854,32 @@ module.exports = class bitfinex2 extends bitfinex {
'limit': limit,
};
const response = await this.publicGetCandlesTradeTimeframeSymbolHist (this.extend (request, params));
//
// [
// [1591503840000,0.025069,0.025068,0.025069,0.025068,1.97828998],
// [1591504500000,0.025065,0.025065,0.025065,0.025065,1.0164],
// [1591504620000,0.025062,0.025062,0.025062,0.025062,0.5],
// ]
//
return this.parseOHLCVs (response, market, timeframe, since, limit);
}
parseOrderStatus (status) {
if (status === undefined) {
return status;
}
const parts = status.split (' ');
const state = this.safeString (parts, 0);
const statuses = {
'ACTIVE': 'open',
'PARTIALLY FILLED': 'open',
'PARTIALLY': 'open',
'EXECUTED': 'closed',
'CANCELED': 'canceled',
'CANCELED was: PARTIALLY FILLED': 'canceled',
'INSUFFICIENT MARGIN': 'canceled',
'INSUFFICIENT BALANCE (G1) was: PARTIALLY FILLED': 'canceled',
'INSUFFICIENT': 'canceled',
'RSN_DUST': 'rejected',
'RSN_PAUSE': 'rejected',
};
return this.safeString (statuses, status, status);
return this.safeString (statuses, state, status);
}
parseOrder (order, market = undefined) {
@@ -748,8 +922,10 @@ module.exports = class bitfinex2 extends bitfinex {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': cost,
'average': average,
@@ -775,6 +951,11 @@ module.exports = class bitfinex2 extends bitfinex {
if (type !== 'market') {
request['price'] = this.numberToString (price);
}
const clientOrderId = this.safeValue2 (params, 'cid', 'clientOrderId');
if (clientOrderId !== undefined) {
request['cid'] = clientOrderId;
params = this.omit (params, [ 'cid', 'clientOrderId' ]);
}
const response = await this.privatePostAuthWOrderSubmit (this.extend (request, params));
//
// [
@@ -844,17 +1025,18 @@ module.exports = class bitfinex2 extends bitfinex {
}
async cancelOrder (id, symbol = undefined, params = {}) {
const cid = this.safeValue (params, 'cid'); // client order id
const cid = this.safeValue2 (params, 'cid', 'clientOrderId'); // client order id
let request = undefined;
if (cid !== undefined) {
const cidDate = this.safeValue (params, 'cidDate'); // client order id date
if (cidDate === undefined) {
throw new InvalidOrder (this.id + " canceling an order by client order id ('cid') requires both 'cid' and 'cid_date' ('YYYY-MM-DD')");
throw new InvalidOrder (this.id + " canceling an order by clientOrderId ('cid') requires both 'cid' and 'cid_date' ('YYYY-MM-DD')");
}
request = {
'cid': cid,
'cid_date': cidDate,
};
params = this.omit (params, [ 'cid', 'clientOrderId' ]);
} else {
request = {
'id': parseInt (id),
@@ -1015,6 +1197,16 @@ module.exports = class bitfinex2 extends bitfinex {
};
}
parseTransactionStatus (status) {
const statuses = {
'SUCCESS': 'ok',
'ERROR': 'failed',
'FAILURE': 'failed',
'CANCELED': 'canceled',
};
return this.safeString (statuses, status, status);
}
parseTransaction (transaction, currency = undefined) {
//
// withdraw
@@ -1040,38 +1232,101 @@ module.exports = class bitfinex2 extends bitfinex {
// "Invalid bitcoin address (abcdef)", // TEXT Text of the notification
// ]
//
// todo add support for all movements, deposits and withdrawals
// fetchTransactions
//
const data = this.safeValue (transaction, 4, []);
const timestamp = this.safeInteger (transaction, 0);
// [
// 13293039, // ID
// 'ETH', // CURRENCY
// 'ETHEREUM', // CURRENCY_NAME
// null,
// null,
// 1574175052000, // MTS_STARTED
// 1574181326000, // MTS_UPDATED
// null,
// null,
// 'CANCELED', // STATUS
// null,
// null,
// -0.24, // AMOUNT, negative for withdrawals
// -0.00135, // FEES
// null,
// null,
// 'DESTINATION_ADDRESS',
// null,
// null,
// null,
// 'TRANSACTION_ID',
// "Purchase of 100 pizzas", // WITHDRAW_TRANSACTION_NOTE
// ]
//
const transactionLength = transaction.length;
let timestamp = undefined;
let updated = undefined;
let code = undefined;
if (currency !== undefined) {
code = currency['code'];
}
let feeCost = this.safeFloat (data, 8);
if (feeCost !== undefined) {
feeCost = Math.abs (feeCost);
}
const amount = this.safeFloat (data, 5);
let id = this.safeValue (data, 0);
let status = 'ok';
if (id === 0) {
id = undefined;
status = 'failed';
let amount = undefined;
let id = undefined;
let status = undefined;
let tag = undefined;
let type = undefined;
let feeCost = undefined;
let txid = undefined;
let addressTo = undefined;
if (transactionLength < 9) {
const data = this.safeValue (transaction, 4, []);
timestamp = this.safeInteger (transaction, 0);
if (currency !== undefined) {
code = currency['code'];
}
feeCost = this.safeFloat (data, 8);
if (feeCost !== undefined) {
feeCost = -feeCost;
}
amount = this.safeFloat (data, 5);
id = this.safeValue (data, 0);
status = 'ok';
if (id === 0) {
id = undefined;
status = 'failed';
}
tag = this.safeString (data, 3);
type = 'withdrawal';
} else {
id = this.safeString (transaction, 0);
timestamp = this.safeInteger (transaction, 5);
updated = this.safeInteger (transaction, 6);
status = this.parseTransactionStatus (this.safeString (transaction, 9));
amount = this.safeFloat (transaction, 12);
if (amount !== undefined) {
if (amount < 0) {
type = 'withdrawal';
} else {
type = 'deposit';
}
}
feeCost = this.safeFloat (transaction, 13);
if (feeCost !== undefined) {
feeCost = -feeCost;
}
addressTo = this.safeString (transaction, 16);
txid = this.safeString (transaction, 20);
}
return {
'info': transaction,
'id': id,
'txid': undefined,
'txid': txid,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'address': undefined, // this is actually the tag for XRP transfers (the address is missing)
'tag': this.safeString (data, 3), // refix it properly for the tag from description
'type': 'withdrawal',
'addressFrom': undefined,
'address': addressTo, // this is actually the tag for XRP transfers (the address is missing)
'addressTo': addressTo,
'tagFrom': undefined,
'tag': tag, // refix it properly for the tag from description
'tagTo': tag,
'type': type,
'amount': amount,
'currency': code,
'status': status,
'updated': undefined,
'updated': updated,
'fee': {
'currency': code,
'cost': feeCost,
@@ -1080,6 +1335,54 @@ module.exports = class bitfinex2 extends bitfinex {
};
}
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
let currency = undefined;
const request = {};
let method = 'privatePostAuthRMovementsHist';
if (code !== undefined) {
currency = this.currency (code);
request['currency'] = currency['id'];
method = 'privatePostAuthRMovementsCurrencyHist';
}
if (since !== undefined) {
request['start'] = since;
}
if (limit !== undefined) {
request['limit'] = limit; // max 1000
}
const response = await this[method] (this.extend (request, params));
//
// [
// [
// 13293039, // ID
// 'ETH', // CURRENCY
// 'ETHEREUM', // CURRENCY_NAME
// null,
// null,
// 1574175052000, // MTS_STARTED
// 1574181326000, // MTS_UPDATED
// null,
// null,
// 'CANCELED', // STATUS
// null,
// null,
// -0.24, // AMOUNT, negative for withdrawals
// -0.00135, // FEES
// null,
// null,
// 'DESTINATION_ADDRESS',
// null,
// null,
// null,
// 'TRANSACTION_ID',
// "Purchase of 100 pizzas", // WITHDRAW_TRANSACTION_NOTE
// ]
// ]
//
return this.parseTransactions (response, currency, since, limit);
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
this.checkAddress (address);
await this.loadMarkets ();
@@ -1128,6 +1431,47 @@ module.exports = class bitfinex2 extends bitfinex {
});
}
async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.privatePostPositions (params);
//
// [
// [
// "tBTCUSD", // SYMBOL
// "ACTIVE", // STATUS
// 0.0195, // AMOUNT
// 8565.0267019, // BASE_PRICE
// 0, // MARGIN_FUNDING
// 0, // MARGIN_FUNDING_TYPE
// -0.33455568705000516, // PL
// -0.0003117550117425625, // PL_PERC
// 7045.876419249083, // PRICE_LIQ
// 3.0673001895895604, // LEVERAGE
// null, // _PLACEHOLDER
// 142355652, // POSITION_ID
// 1574002216000, // MTS_CREATE
// 1574002216000, // MTS_UPDATE
// null, // _PLACEHOLDER
// 0, // TYPE
// null, // _PLACEHOLDER
// 0, // COLLATERAL
// 0, // COLLATERAL_MIN
// // META
// {
// "reason":"TRADE",
// "order_id":34271018124,
// "liq_stage":null,
// "trade_price":"8565.0267019",
// "trade_amount":"0.0195",
// "order_id_oppo":34277498022
// }
// ]
// ]
//
// todo unify parsePosition/parsePositions
return response;
}
nonce () {
return this.milliseconds ();
}
@@ -15,19 +15,27 @@ module.exports = class bitflyer extends Exchange {
'countries': [ 'JP' ],
'version': 'v1',
'rateLimit': 1000, // their nonce-timestamp is in seconds...
'hostname': 'bitflyer.com', // or bitflyer.com
'has': {
'cancelOrder': true,
'CORS': false,
'withdraw': true,
'fetchMyTrades': true,
'fetchOrders': true,
'fetchOrder': 'emulated',
'fetchOpenOrders': 'emulated',
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': 'emulated',
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': 'emulated',
'fetchOrder': 'emulated',
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTrades': true,
'withdraw': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg',
'api': 'https://api.bitflyer.jp',
'www': 'https://bitflyer.jp',
'api': 'https://api.{hostname}',
'www': 'https://bitflyer.com',
'doc': 'https://lightning.bitflyer.com/docs?lang=en',
},
'api': {
@@ -334,16 +342,8 @@ module.exports = class bitflyer extends Exchange {
const status = this.parseOrderStatus (this.safeString (order, 'child_order_state'));
const type = this.safeStringLower (order, 'child_order_type');
const side = this.safeStringLower (order, 'side');
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (order, 'product_code');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
}
if (market !== undefined) {
symbol = market['symbol'];
}
const marketId = this.safeString (order, 'product_code');
const symbol = this.safeSymbol (marketId, market);
let fee = undefined;
const feeCost = this.safeFloat (order, 'total_commission');
if (feeCost !== undefined) {
@@ -364,8 +364,10 @@ module.exports = class bitflyer extends Exchange {
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -436,6 +438,36 @@ module.exports = class bitflyer extends Exchange {
return this.parseTrades (response, market, since, limit);
}
async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) {
if (symbols === undefined) {
throw new ArgumentsRequired (this.id + ' fetchPositions requires a `symbols` argument, exactly one symbol in an array');
}
await this.loadMarkets ();
const request = {
'product_code': this.marketIds (symbols),
};
const response = await this.privateGetpositions (this.extend (request, params));
//
// [
// {
// "product_code": "FX_BTC_JPY",
// "side": "BUY",
// "price": 36000,
// "size": 10,
// "commission": 0,
// "swap_point_accumulate": -35,
// "require_collateral": 120000,
// "open_date": "2015-11-03T10:04:45.011",
// "leverage": 3,
// "pnl": 965,
// "sfd": -0.5
// }
// ]
//
// todo unify parsePosition/parsePositions
return response;
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
this.checkAddress (address);
await this.loadMarkets ();
@@ -467,7 +499,8 @@ module.exports = class bitflyer extends Exchange {
request += '?' + this.urlencode (params);
}
}
const url = this.urls['api'] + request;
const baseUrl = this.implodeParams (this.urls['api'], { 'hostname': this.hostname });
const url = baseUrl + request;
if (api === 'private') {
this.checkRequiredCredentials ();
const nonce = this.nonce ().toString ();
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, AuthenticationError, OrderNotFound, InsufficientFunds, DDoSProtection } = require ('./base/errors');
const { ExchangeError, AuthenticationError, OrderNotFound, InsufficientFunds, DDoSProtection, PermissionDenied } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -15,19 +15,20 @@ module.exports = class bitforex extends Exchange {
'countries': [ 'CN' ],
'version': 'v1',
'has': {
'fetchBalance': true,
'fetchMarkets': true,
'createOrder': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchMarkets': true,
'fetchMyTrades': false,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': false,
'fetchTicker': true,
'fetchTickers': false,
'fetchMyTrades': false,
'fetchTrades': true,
'fetchOrder': true,
'fetchOrders': false,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchOHLCV': true,
},
'timeframes': {
'1m': '1min',
@@ -43,7 +44,7 @@ module.exports = class bitforex extends Exchange {
'1M': '1month',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/44310033-69e9e600-a3d8-11e8-873d-54d74d1bc4e4.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87295553-1160ec00-c50e-11ea-8ea0-df79276a9646.jpg',
'api': 'https://api.bitforex.com',
'www': 'https://www.bitforex.com',
'doc': 'https://github.com/githubdev2020/API_Doc_en/wiki',
@@ -221,12 +222,15 @@ module.exports = class bitforex extends Exchange {
},
},
'commonCurrencies': {
'CREDIT': 'TerraCredit',
'IQ': 'IQ.Cash',
'UOS': 'UOS Network',
},
'exceptions': {
'4004': OrderNotFound,
'1013': AuthenticationError,
'1016': AuthenticationError,
'1017': PermissionDenied, // {"code":"1017","success":false,"time":1602670594367,"message":"IP not allow"}
'3002': InsufficientFunds,
'10204': DDoSProtection,
},
@@ -380,7 +384,18 @@ module.exports = class bitforex extends Exchange {
};
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "close":0.02505143,
// "currencyVol":0,
// "high":0.02506422,
// "low":0.02505143,
// "open":0.02506095,
// "time":1591508940000,
// "vol":51.1869
// }
//
return [
this.safeInteger (ohlcv, 'time'),
this.safeFloat (ohlcv, 'open'),
@@ -402,8 +417,19 @@ module.exports = class bitforex extends Exchange {
request['size'] = limit; // default 1, max 600
}
const response = await this.publicGetApiV1MarketKline (this.extend (request, params));
const ohlcvs = this.safeValue (response, 'data', []);
return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
//
// {
// "data":[
// {"close":0.02505143,"currencyVol":0,"high":0.02506422,"low":0.02505143,"open":0.02506095,"time":1591508940000,"vol":51.1869},
// {"close":0.02503914,"currencyVol":0,"high":0.02506687,"low":0.02503914,"open":0.02505358,"time":1591509000000,"vol":9.1082},
// {"close":0.02505172,"currencyVol":0,"high":0.02507466,"low":0.02503895,"open":0.02506371,"time":1591509060000,"vol":63.7431},
// ],
// "success":true,
// "time":1591509427131
// }
//
const data = this.safeValue (response, 'data', []);
return this.parseOHLCVs (data, market, timeframe, since, limit);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -472,8 +498,10 @@ module.exports = class bitforex extends Exchange {
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'average': average,
'amount': amount,
File diff suppressed because it is too large. Load diff
@@ -4,6 +4,7 @@
const Exchange = require ('./base/Exchange');
const { ExchangeError, ExchangeNotAvailable, AuthenticationError, BadRequest, PermissionDenied, InvalidAddress, ArgumentsRequired, InvalidOrder } = require ('./base/errors');
const { DECIMAL_PLACES, SIGNIFICANT_DIGITS, TRUNCATE } = require ('./base/functions/number');
// ---------------------------------------------------------------------------
@@ -15,13 +16,18 @@ module.exports = class bithumb extends Exchange {
'countries': [ 'KR' ], // South Korea
'rateLimit': 500,
'has': {
'CORS': true,
'createOrder': true,
'cancelOrder': true,
'CORS': true,
'createMarketOrder': true,
'fetchTickers': true,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'withdraw': true,
},
'urls': {
@@ -70,6 +76,7 @@ module.exports = class bithumb extends Exchange {
'taker': 0.25 / 100,
},
},
'precisionMode': SIGNIFICANT_DIGITS,
'exceptions': {
'Bad Request(SSL)': BadRequest,
'Bad Request(Bad Method)': BadRequest,
@@ -88,19 +95,23 @@ module.exports = class bithumb extends Exchange {
});
}
amountToPrecision (symbol, amount) {
return this.decimalToPrecision (amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES);
}
async fetchMarkets (params = {}) {
const response = await this.publicGetTickerAll (params);
const data = this.safeValue (response, 'data');
const currencyIds = Object.keys (data);
const result = [];
const quote = this.safeCurrencyCode ('KRW');
for (let i = 0; i < currencyIds.length; i++) {
const currencyId = currencyIds[i];
if (currencyId === 'date') {
continue;
}
const market = data[currencyId];
const base = currencyId;
const quote = 'KRW';
const base = this.safeCurrencyCode (currencyId);
const symbol = currencyId + '/' + quote;
let active = true;
if (Array.isArray (market)) {
@@ -117,8 +128,8 @@ module.exports = class bithumb extends Exchange {
'info': market,
'active': active,
'precision': {
'amount': undefined,
'price': undefined,
'amount': 4,
'price': 4,
},
'limits': {
'amount': {
@@ -130,8 +141,8 @@ module.exports = class bithumb extends Exchange {
'max': undefined,
},
'cost': {
'min': undefined,
'max': undefined,
'min': 500,
'max': 5000000000,
},
},
'baseId': undefined,
@@ -236,10 +247,7 @@ module.exports = class bithumb extends Exchange {
}
const baseVolume = this.safeFloat (ticker, 'units_traded_24H');
const quoteVolume = this.safeFloat (ticker, 'acc_trade_value_24H');
let vwap = undefined;
if (quoteVolume !== undefined && baseVolume !== undefined) {
vwap = quoteVolume / baseVolume;
}
const vwap = this.vwap (baseVolume, quoteVolume);
return {
'symbol': symbol,
'timestamp': timestamp,
@@ -308,7 +316,7 @@ module.exports = class bithumb extends Exchange {
result[symbol] = this.parseTicker (ticker, market);
}
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -393,7 +401,7 @@ module.exports = class bithumb extends Exchange {
symbol = market['symbol'];
}
const price = this.safeFloat (trade, 'price');
const amount = this.safeFloat (trade, 'units_traded');
const amount = this.safeFloat2 (trade, 'units_traded', 'units');
let cost = this.safeFloat (trade, 'total');
if (cost === undefined) {
if (amount !== undefined) {
@@ -503,25 +511,26 @@ module.exports = class bithumb extends Exchange {
// {
// "status": "0000",
// "data": {
// "transaction_date": "1572497603668315",
// "type": "bid",
// "order_status": "Completed",
// "order_currency": "BTC",
// "payment_currency": "KRW",
// "order_price": "8601000",
// "order_qty": "0.007",
// "cancel_date": "",
// "cancel_type": "",
// "contract": [
// order_date: '1603161798539254',
// type: 'ask',
// order_status: 'Cancel',
// order_currency: 'BTC',
// payment_currency: 'KRW',
// watch_price: '0',
// order_price: '13344000',
// order_qty: '0.0125',
// cancel_date: '1603161803809993',
// cancel_type: '사용자취소',
// contract: [
// {
// "transaction_date": "1572497603902030",
// "price": "8601000",
// "units": "0.005",
// "fee_currency": "KRW",
// "fee": "107.51",
// "total": "43005"
// },
// ]
// transaction_date: '1603161799976383',
// price: '13344000',
// units: '0.0015',
// fee_currency: 'KRW',
// fee: '0',
// total: '20016'
// }
// ],
// }
// }
//
@@ -539,6 +548,7 @@ module.exports = class bithumb extends Exchange {
}
parseOrder (order, market = undefined) {
//
//
// fetchOrder
//
@@ -564,6 +574,29 @@ module.exports = class bithumb extends Exchange {
// ]
// }
//
// {
// order_date: '1603161798539254',
// type: 'ask',
// order_status: 'Cancel',
// order_currency: 'BTC',
// payment_currency: 'KRW',
// watch_price: '0',
// order_price: '13344000',
// order_qty: '0.0125',
// cancel_date: '1603161803809993',
// cancel_type: '사용자취소',
// contract: [
// {
// transaction_date: '1603161799976383',
// price: '13344000',
// units: '0.0015',
// fee_currency: 'KRW',
// fee: '0',
// total: '20016'
// }
// ],
// }
//
// fetchOpenOrders
//
// {
@@ -592,14 +625,10 @@ module.exports = class bithumb extends Exchange {
if (remaining === undefined) {
if (status === 'closed') {
remaining = 0;
} else {
} else if (status !== 'canceled') {
remaining = amount;
}
}
let filled = undefined;
if ((amount !== undefined) && (remaining !== undefined)) {
filled = amount - remaining;
}
let symbol = undefined;
const baseId = this.safeString (order, 'order_currency');
const quoteId = this.safeString (order, 'payment_currency');
@@ -611,35 +640,92 @@ module.exports = class bithumb extends Exchange {
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
let filled = undefined;
let cost = undefined;
let average = undefined;
const id = this.safeString (order, 'order_id');
const rawTrades = this.safeValue (order, 'contract');
let trades = undefined;
const id = this.safeString (order, 'order_id');
let fee = undefined;
let fees = undefined;
let feesByCurrency = undefined;
if (rawTrades !== undefined) {
trades = this.parseTrades (rawTrades, market, undefined, undefined, {
'side': side,
'symbol': symbol,
'order': id,
});
filled = 0;
feesByCurrency = {};
for (let i = 0; i < trades.length; i++) {
const trade = trades[i];
filled = this.sum (filled, trade['amount']);
cost = this.sum (cost, trade['cost']);
const tradeFee = trade['fee'];
const feeCurrency = tradeFee['currency'];
if (feeCurrency in feesByCurrency) {
feesByCurrency[feeCurrency] = {
'currency': feeCurrency,
'cost': this.sum (feesByCurrency[feeCurrency]['cost'], tradeFee['cost']),
};
} else {
feesByCurrency[feeCurrency] = {
'currency': feeCurrency,
'cost': tradeFee['cost'],
};
}
}
const feeCurrencies = Object.keys (feesByCurrency);
const feeCurrenciesLength = feeCurrencies.length;
if (feeCurrenciesLength > 1) {
fees = [];
for (let i = 0; i < feeCurrencies.length; i++) {
const feeCurrency = feeCurrencies[i];
fees.push (feesByCurrency[feeCurrency]);
}
} else {
fee = this.safeValue (feesByCurrency, feeCurrencies[0]);
}
if (filled !== 0) {
average = cost / filled;
}
}
return {
if (amount !== undefined) {
if ((filled === undefined) && (remaining !== undefined)) {
filled = Math.max (0, amount - remaining);
}
if ((remaining === undefined) && (filled !== undefined)) {
remaining = Math.max (0, amount - filled);
}
}
const result = {
'info': order,
'id': id,
'clientOrderId': undefined,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': undefined,
'average': undefined,
'cost': cost,
'average': average,
'filled': filled,
'remaining': remaining,
'status': status,
'fee': undefined,
'trades': trades,
};
if (fee !== undefined) {
result['fee'] = fee;
} else if (fees !== undefined) {
result['fees'] = fees;
}
return result;
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -753,12 +839,12 @@ module.exports = class bithumb extends Exchange {
const nonce = this.nonce ().toString ();
const auth = endpoint + "\0" + body + "\0" + nonce; // eslint-disable-line quotes
const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512');
const signature64 = this.decode (this.stringToBase64 (this.encode (signature)));
const signature64 = this.decode (this.stringToBase64 (signature));
headers = {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Api-Key': this.apiKey,
'Api-Sign': signature64.toString (),
'Api-Sign': signature64,
'Api-Nonce': nonce,
};
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -19,17 +19,26 @@ module.exports = class bitmex extends Exchange {
'rateLimit': 2000,
'pro': true,
'has': {
'cancelAllOrders': true,
'cancelOrder': true,
'CORS': false,
'fetchOHLCV': true,
'withdraw': true,
'createOrder': true,
'editOrder': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOpenOrders': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchMyTrades': true,
'fetchLedger': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchTransactions': 'emulated',
'withdraw': true,
},
'timeframes': {
'1m': '1m',
@@ -99,6 +108,7 @@ module.exports = class bitmex extends Exchange {
'user/checkReferralCode',
'user/commission',
'user/depositAddress',
'user/executionHistory',
'user/margin',
'user/minWithdrawalFee',
'user/wallet',
@@ -156,6 +166,7 @@ module.exports = class bitmex extends Exchange {
'overloaded': ExchangeNotAvailable,
'Account has insufficient Available Balance': InsufficientFunds,
'Service unavailable': ExchangeNotAvailable, // {"error":{"message":"Service unavailable","name":"HTTPError"}}
'Server Error': ExchangeError, // {"error":{"message":"Server Error","name":"HTTPError"}}
},
},
'precisionMode': TICK_SIZE,
@@ -253,29 +264,133 @@ module.exports = class bitmex extends Exchange {
return result;
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const request = {
'currency': 'all',
};
const response = await this.privateGetUserMargin (this.extend (request, params));
parseBalanceResponse (response) {
//
// [
// {
// "account":1455728,
// "currency":"XBt",
// "riskLimit":1000000000000,
// "prevState":"",
// "state":"",
// "action":"",
// "amount":263542,
// "pendingCredit":0,
// "pendingDebit":0,
// "confirmedDebit":0,
// "prevRealisedPnl":0,
// "prevUnrealisedPnl":0,
// "grossComm":0,
// "grossOpenCost":0,
// "grossOpenPremium":0,
// "grossExecCost":0,
// "grossMarkValue":0,
// "riskValue":0,
// "taxableMargin":0,
// "initMargin":0,
// "maintMargin":0,
// "sessionMargin":0,
// "targetExcessMargin":0,
// "varMargin":0,
// "realisedPnl":0,
// "unrealisedPnl":0,
// "indicativeTax":0,
// "unrealisedProfit":0,
// "syntheticMargin":null,
// "walletBalance":263542,
// "marginBalance":263542,
// "marginBalancePcnt":1,
// "marginLeverage":0,
// "marginUsedPcnt":0,
// "excessMargin":263542,
// "excessMarginPcnt":1,
// "availableMargin":263542,
// "withdrawableMargin":263542,
// "timestamp":"2020-08-03T12:01:01.246Z",
// "grossLastValue":0,
// "commission":null
// }
// ]
//
const result = { 'info': response };
for (let i = 0; i < response.length; i++) {
const balance = response[i];
const currencyId = this.safeString (balance, 'currency');
const code = this.safeCurrencyCode (currencyId);
const account = this.account ();
account['free'] = this.safeFloat (balance, 'availableMargin');
account['total'] = this.safeFloat (balance, 'marginBalance');
let free = this.safeFloat (balance, 'availableMargin');
let total = this.safeFloat (balance, 'marginBalance');
if (code === 'BTC') {
account['free'] = account['free'] * 0.00000001;
account['total'] = account['total'] * 0.00000001;
if (free !== undefined) {
free /= 100000000;
}
if (total !== undefined) {
total /= 100000000;
}
}
account['free'] = free;
account['total'] = total;
result[code] = account;
}
return this.parseBalance (result);
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const request = {
'currency': 'all',
};
const response = await this.privateGetUserMargin (this.extend (request, params));
//
// [
// {
// "account":1455728,
// "currency":"XBt",
// "riskLimit":1000000000000,
// "prevState":"",
// "state":"",
// "action":"",
// "amount":263542,
// "pendingCredit":0,
// "pendingDebit":0,
// "confirmedDebit":0,
// "prevRealisedPnl":0,
// "prevUnrealisedPnl":0,
// "grossComm":0,
// "grossOpenCost":0,
// "grossOpenPremium":0,
// "grossExecCost":0,
// "grossMarkValue":0,
// "riskValue":0,
// "taxableMargin":0,
// "initMargin":0,
// "maintMargin":0,
// "sessionMargin":0,
// "targetExcessMargin":0,
// "varMargin":0,
// "realisedPnl":0,
// "unrealisedPnl":0,
// "indicativeTax":0,
// "unrealisedProfit":0,
// "syntheticMargin":null,
// "walletBalance":263542,
// "marginBalance":263542,
// "marginBalancePcnt":1,
// "marginLeverage":0,
// "marginUsedPcnt":0,
// "excessMargin":263542,
// "excessMarginPcnt":1,
// "availableMargin":263542,
// "withdrawableMargin":263542,
// "timestamp":"2020-08-03T12:01:01.246Z",
// "grossLastValue":0,
// "commission":null
// }
// ]
//
return this.parseBalanceResponse (response);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
@@ -505,7 +620,7 @@ module.exports = class bitmex extends Exchange {
const code = this.safeCurrencyCode (currencyId, currency);
let amount = this.safeFloat (item, 'amount');
if (amount !== undefined) {
amount = amount * 1e-8;
amount = amount / 100000000;
}
let timestamp = this.parse8601 (this.safeString (item, 'transactTime'));
if (timestamp === undefined) {
@@ -516,7 +631,7 @@ module.exports = class bitmex extends Exchange {
}
let feeCost = this.safeFloat (item, 'fee', 0);
if (feeCost !== undefined) {
feeCost = feeCost * 1e-8;
feeCost = feeCost / 100000000;
}
const fee = {
'cost': feeCost,
@@ -524,7 +639,7 @@ module.exports = class bitmex extends Exchange {
};
let after = this.safeFloat (item, 'walletBalance');
if (after !== undefined) {
after = after * 1e-8;
after = after / 100000000;
}
const before = this.sum (after, -amount);
let direction = undefined;
@@ -662,11 +777,11 @@ module.exports = class bitmex extends Exchange {
}
let amount = this.safeInteger (transaction, 'amount');
if (amount !== undefined) {
amount = Math.abs (amount) * 1e-8;
amount = Math.abs (amount) / 10000000;
}
let feeCost = this.safeInteger (transaction, 'fee');
if (feeCost !== undefined) {
feeCost = feeCost * 1e-8;
feeCost = feeCost / 10000000;
}
const fee = {
'cost': feeCost,
@@ -724,7 +839,7 @@ module.exports = class bitmex extends Exchange {
result[symbol] = ticker;
}
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
parseTicker (ticker, market = undefined) {
@@ -875,10 +990,26 @@ module.exports = class bitmex extends Exchange {
};
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
const timestamp = this.parse8601 (this.safeString (ohlcv, 'timestamp'));
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "timestamp":"2015-09-25T13:38:00.000Z",
// "symbol":"XBTUSD",
// "open":237.45,
// "high":237.45,
// "low":237.45,
// "close":237.45,
// "trades":0,
// "volume":0,
// "vwap":null,
// "lastSize":null,
// "turnover":0,
// "homeNotional":0,
// "foreignNotional":0
// }
//
return [
timestamp,
this.parse8601 (this.safeString (ohlcv, 'timestamp')),
this.safeFloat (ohlcv, 'open'),
this.safeFloat (ohlcv, 'high'),
this.safeFloat (ohlcv, 'low'),
@@ -919,8 +1050,17 @@ module.exports = class bitmex extends Exchange {
}
const ymdhms = this.ymdhms (timestamp);
request['startTime'] = ymdhms; // starting date filter for results
} else {
request['reverse'] = true;
}
const response = await this.publicGetTradeBucketed (this.extend (request, params));
//
// [
// {"timestamp":"2015-09-25T13:38:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0},
// {"timestamp":"2015-09-25T13:39:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0},
// {"timestamp":"2015-09-25T13:40:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}
// ]
//
const result = this.parseOHLCVs (response, market, timeframe, since, limit);
if (fetchOHLCVOpenTimestamp) {
// bitmex returns the candle's close timestamp - https://github.com/ccxt/ccxt/issues/4446
@@ -1003,7 +1143,7 @@ module.exports = class bitmex extends Exchange {
// }
//
const timestamp = this.parse8601 (this.safeString (trade, 'timestamp'));
const price = this.safeFloat (trade, 'price');
const price = this.safeFloat2 (trade, 'avgPx', 'price');
const amount = this.safeFloat2 (trade, 'size', 'lastQty');
const id = this.safeString (trade, 'trdMatchID');
const order = this.safeString (trade, 'orderID');
@@ -1030,16 +1170,8 @@ module.exports = class bitmex extends Exchange {
if (fee !== undefined) {
takerOrMaker = (fee['cost'] < 0) ? 'maker' : 'taker';
}
let symbol = undefined;
const marketId = this.safeString (trade, 'symbol');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
symbol = marketId;
}
}
const symbol = this.safeSymbol (marketId, market);
const type = this.safeStringLower (trade, 'ordType');
return {
'info': trade,
@@ -1076,18 +1208,20 @@ module.exports = class bitmex extends Exchange {
return this.safeString (statuses, status, status);
}
parseTimeInForce (timeInForce) {
const timeInForces = {
'Day': 'Day',
'GoodTillCancel': 'GTC',
'ImmediateOrCancel': 'IOC',
'FillOrKill': 'FOK',
};
return this.safeString (timeInForces, timeInForce, timeInForce);
}
parseOrder (order, market = undefined) {
const status = this.parseOrderStatus (this.safeString (order, 'ordStatus'));
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
} else {
const marketId = this.safeString (order, 'symbol');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
}
}
const marketId = this.safeString (order, 'symbol');
const symbol = this.safeSymbol (marketId, market);
const timestamp = this.parse8601 (this.safeString (order, 'timestamp'));
const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'transactTime'));
const price = this.safeFloat (order, 'price');
@@ -1112,6 +1246,8 @@ module.exports = class bitmex extends Exchange {
const type = this.safeStringLower (order, 'ordType');
const side = this.safeStringLower (order, 'side');
const clientOrderId = this.safeString (order, 'clOrdID');
const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce'));
const stopPrice = this.safeFloat (order, 'stopPx');
return {
'info': order,
'id': id,
@@ -1121,8 +1257,10 @@ module.exports = class bitmex extends Exchange {
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'side': side,
'price': price,
'stopPrice': stopPrice,
'amount': amount,
'cost': cost,
'average': average,
@@ -1142,6 +1280,9 @@ module.exports = class bitmex extends Exchange {
};
if (since !== undefined) {
request['startTime'] = this.iso8601 (since);
} else {
// by default reverse=false, i.e. trades are fetched since the time of market inception (year 2015 for XBTUSD)
request['reverse'] = true;
}
if (limit !== undefined) {
request['count'] = limit;
@@ -1180,27 +1321,46 @@ module.exports = class bitmex extends Exchange {
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const orderType = this.capitalize (type);
const request = {
'symbol': this.marketId (symbol),
'symbol': market['id'],
'side': this.capitalize (side),
'orderQty': amount,
'ordType': this.capitalize (type),
'ordType': orderType,
};
if (price !== undefined) {
request['price'] = price;
if (orderType === 'Stop') {
const stopPrice = this.safeFloat2 (params, 'stopPx', 'stopPrice');
request['stopPx'] = stopPrice;
params = this.omit (params, [ 'stopPx', 'stopPrice' ]);
} else {
request['price'] = price;
}
}
const clientOrderId = this.safeString2 (params, 'clOrdID', 'clientOrderId');
if (clientOrderId !== undefined) {
request['clOrdID'] = clientOrderId;
params = this.omit (params, [ 'clOrdID', 'clientOrderId' ]);
}
const response = await this.privatePostOrder (this.extend (request, params));
const order = this.parseOrder (response);
const id = this.safeString (order, 'id');
this.orders[id] = order;
return this.extend ({ 'info': response }, order);
return this.parseOrder (response, market);
}
async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) {
await this.loadMarkets ();
const request = {
'orderID': id,
};
const request = {};
const origClOrdID = this.safeString2 (params, 'origClOrdID', 'clientOrderId');
if (origClOrdID !== undefined) {
request['origClOrdID'] = origClOrdID;
const clientOrderId = this.safeString (params, 'clOrdID', 'clientOrderId');
if (clientOrderId !== undefined) {
request['clOrdID'] = clientOrderId;
}
params = this.omit (params, [ 'origClOrdID', 'clOrdID', 'clientOrderId' ]);
} else {
request['orderID'] = id;
}
if (amount !== undefined) {
request['orderQty'] = amount;
}
@@ -1208,32 +1368,183 @@ module.exports = class bitmex extends Exchange {
request['price'] = price;
}
const response = await this.privatePutOrder (this.extend (request, params));
const order = this.parseOrder (response);
this.orders[order['id']] = order;
return this.extend ({ 'info': response }, order);
return this.parseOrder (response);
}
async cancelOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
// https://github.com/ccxt/ccxt/issues/6507
const clOrdID = this.safeValue (params, 'clOrdID');
const clientOrderId = this.safeString2 (params, 'clOrdID', 'clientOrderId');
const request = {};
if (clOrdID === undefined) {
if (clientOrderId === undefined) {
request['orderID'] = id;
} else {
request['clOrdID'] = clOrdID;
request['clOrdID'] = clientOrderId;
params = this.omit (params, [ 'clOrdID', 'clientOrderId' ]);
}
const response = await this.privateDeleteOrder (this.extend (request, params));
let order = response[0];
const order = this.safeValue (response, 0, {});
const error = this.safeString (order, 'error');
if (error !== undefined) {
if (error.indexOf ('Unable to cancel order due to existing state') >= 0) {
throw new OrderNotFound (this.id + ' cancelOrder() failed: ' + error);
}
}
order = this.parseOrder (order);
this.orders[order['id']] = order;
return this.extend ({ 'info': response }, order);
return this.parseOrder (order);
}
async cancelAllOrders (symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['symbol'] = market['id'];
}
const response = await this.privateDeleteOrderAll (this.extend (request, params));
//
// [
// {
// "orderID": "string",
// "clOrdID": "string",
// "clOrdLinkID": "string",
// "account": 0,
// "symbol": "string",
// "side": "string",
// "simpleOrderQty": 0,
// "orderQty": 0,
// "price": 0,
// "displayQty": 0,
// "stopPx": 0,
// "pegOffsetValue": 0,
// "pegPriceType": "string",
// "currency": "string",
// "settlCurrency": "string",
// "ordType": "string",
// "timeInForce": "string",
// "execInst": "string",
// "contingencyType": "string",
// "exDestination": "string",
// "ordStatus": "string",
// "triggered": "string",
// "workingIndicator": true,
// "ordRejReason": "string",
// "simpleLeavesQty": 0,
// "leavesQty": 0,
// "simpleCumQty": 0,
// "cumQty": 0,
// "avgPx": 0,
// "multiLegReportingType": "string",
// "text": "string",
// "transactTime": "2020-06-01T09:36:35.290Z",
// "timestamp": "2020-06-01T09:36:35.290Z"
// }
// ]
//
return this.parseOrders (response, market);
}
async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.privateGetPosition (params);
// [
// {
// "account": 0,
// "symbol": "string",
// "currency": "string",
// "underlying": "string",
// "quoteCurrency": "string",
// "commission": 0,
// "initMarginReq": 0,
// "maintMarginReq": 0,
// "riskLimit": 0,
// "leverage": 0,
// "crossMargin": true,
// "deleveragePercentile": 0,
// "rebalancedPnl": 0,
// "prevRealisedPnl": 0,
// "prevUnrealisedPnl": 0,
// "prevClosePrice": 0,
// "openingTimestamp": "2020-11-09T06:53:59.892Z",
// "openingQty": 0,
// "openingCost": 0,
// "openingComm": 0,
// "openOrderBuyQty": 0,
// "openOrderBuyCost": 0,
// "openOrderBuyPremium": 0,
// "openOrderSellQty": 0,
// "openOrderSellCost": 0,
// "openOrderSellPremium": 0,
// "execBuyQty": 0,
// "execBuyCost": 0,
// "execSellQty": 0,
// "execSellCost": 0,
// "execQty": 0,
// "execCost": 0,
// "execComm": 0,
// "currentTimestamp": "2020-11-09T06:53:59.893Z",
// "currentQty": 0,
// "currentCost": 0,
// "currentComm": 0,
// "realisedCost": 0,
// "unrealisedCost": 0,
// "grossOpenCost": 0,
// "grossOpenPremium": 0,
// "grossExecCost": 0,
// "isOpen": true,
// "markPrice": 0,
// "markValue": 0,
// "riskValue": 0,
// "homeNotional": 0,
// "foreignNotional": 0,
// "posState": "string",
// "posCost": 0,
// "posCost2": 0,
// "posCross": 0,
// "posInit": 0,
// "posComm": 0,
// "posLoss": 0,
// "posMargin": 0,
// "posMaint": 0,
// "posAllowance": 0,
// "taxableMargin": 0,
// "initMargin": 0,
// "maintMargin": 0,
// "sessionMargin": 0,
// "targetExcessMargin": 0,
// "varMargin": 0,
// "realisedGrossPnl": 0,
// "realisedTax": 0,
// "realisedPnl": 0,
// "unrealisedGrossPnl": 0,
// "longBankrupt": 0,
// "shortBankrupt": 0,
// "taxBase": 0,
// "indicativeTaxRate": 0,
// "indicativeTax": 0,
// "unrealisedTax": 0,
// "unrealisedPnl": 0,
// "unrealisedPnlPcnt": 0,
// "unrealisedRoePcnt": 0,
// "simpleQty": 0,
// "simpleCost": 0,
// "simpleValue": 0,
// "simplePnl": 0,
// "simplePnlPcnt": 0,
// "avgCostPrice": 0,
// "avgEntryPrice": 0,
// "breakEvenPrice": 0,
// "marginCallPrice": 0,
// "liquidationPrice": 0,
// "bankruptPrice": 0,
// "timestamp": "2020-11-09T06:53:59.894Z",
// "lastPrice": 0,
// "lastValue": 0
// }
// ]
//
// todo unify parsePosition/parsePositions
return response;
}
isFiat (currency) {
@@ -1263,7 +1574,7 @@ module.exports = class bitmex extends Exchange {
const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params));
return {
'info': response,
'id': response['transactID'],
'id': this.safeString (response, 'transactID'),
};
}
File diff suppressed because it is too large. Load diff
@@ -4,6 +4,7 @@
const Exchange = require ('./base/Exchange');
const { ExchangeError, InvalidNonce, AuthenticationError, OrderNotFound } = require ('./base/errors');
const { TICK_SIZE } = require ('./base/functions/number');
// ---------------------------------------------------------------------------
@@ -16,26 +17,37 @@ module.exports = class bitso extends Exchange {
'rateLimit': 2000, // 30 requests per minute
'version': 'v3',
'has': {
'cancelOrder': true,
'CORS': false,
'createOrder': true,
'fetchBalance': true,
'fetchDepositAddress': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrderTrades': true,
'fetchTicker': true,
'fetchTrades': true,
'withdraw': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27766335-715ce7aa-5ed5-11e7-88a8-173a27bb30fe.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87295554-11f98280-c50e-11ea-80d6-15b3bafa8cbf.jpg',
'api': 'https://api.bitso.com',
'www': 'https://bitso.com',
'doc': 'https://bitso.com/api_info',
'fees': 'https://bitso.com/fees?l=es',
'fees': 'https://bitso.com/fees',
'referral': 'https://bitso.com/?ref=itej',
},
'precisionMode': TICK_SIZE,
'options': {
'precision': {
'XRP': 6,
'MXN': 2,
'TUSD': 2,
'XRP': 0.000001,
'MXN': 0.01,
'TUSD': 0.01,
},
'defaultPrecision': 8,
'defaultPrecision': 0.00000001,
},
'api': {
'public': {
@@ -100,6 +112,38 @@ module.exports = class bitso extends Exchange {
async fetchMarkets (params = {}) {
const response = await this.publicGetAvailableBooks (params);
//
// {
// "success":true,
// "payload":[
// {
// "book":"btc_mxn",
// "minimum_price":"500",
// "maximum_price":"10000000",
// "minimum_amount":"0.00005",
// "maximum_amount":"500",
// "minimum_value":"5",
// "maximum_value":"10000000",
// "tick_size":"0.01",
// "fees":{
// "flat_rate":{"maker":"0.500","taker":"0.650"},
// "structure":[
// {"volume":"1500000","maker":"0.00500","taker":"0.00650"},
// {"volume":"2000000","maker":"0.00490","taker":"0.00637"},
// {"volume":"5000000","maker":"0.00480","taker":"0.00624"},
// {"volume":"7000000","maker":"0.00440","taker":"0.00572"},
// {"volume":"10000000","maker":"0.00420","taker":"0.00546"},
// {"volume":"15000000","maker":"0.00400","taker":"0.00520"},
// {"volume":"35000000","maker":"0.00370","taker":"0.00481"},
// {"volume":"50000000","maker":"0.00300","taker":"0.00390"},
// {"volume":"150000000","maker":"0.00200","taker":"0.00260"},
// {"volume":"250000000","maker":"0.00100","taker":"0.00130"},
// {"volume":"9999999999","maker":"0.00000","taker":"0.00130"},
// ]
// }
// },
// ]
// }
const markets = this.safeValue (response, 'payload');
const result = [];
for (let i = 0; i < markets.length; i++) {
@@ -125,11 +169,43 @@ module.exports = class bitso extends Exchange {
'max': this.safeFloat (market, 'maximum_value'),
},
};
const defaultPricePrecision = this.safeFloat (this.options['precision'], quote, this.options['defaultPrecision']);
const pricePrecision = this.safeFloat (market, 'tick_size', defaultPricePrecision);
const precision = {
'amount': this.safeInteger (this.options['precision'], base, this.options['defaultPrecision']),
'price': this.safeInteger (this.options['precision'], quote, this.options['defaultPrecision']),
'amount': this.safeFloat (this.options['precision'], base, this.options['defaultPrecision']),
'price': pricePrecision,
};
result.push ({
const fees = this.safeValue (market, 'fees', {});
const flatRate = this.safeValue (fees, 'flat_rate', {});
const maker = this.safeFloat (flatRate, 'maker');
const taker = this.safeFloat (flatRate, 'taker');
const feeTiers = this.safeValue (fees, 'structure', []);
const fee = {
'maker': maker,
'taker': taker,
'percentage': true,
'tierBased': true,
};
const takerFees = [];
const makerFees = [];
for (let i = 0; i < feeTiers.length; i++) {
const tier = feeTiers[i];
const volume = this.safeFloat (tier, 'volume');
const takerFee = this.safeFloat (tier, 'taker');
const makerFee = this.safeFloat (tier, 'maker');
takerFees.push ([ volume, takerFee ]);
makerFees.push ([ volume, makerFee ]);
if (i === 0) {
fee['taker'] = taker;
fee['maker'] = maker;
}
}
const tiers = {
'taker': takerFees,
'maker': makerFees,
};
fee['tiers'] = tiers;
result.push (this.extend ({
'id': id,
'symbol': symbol,
'base': base,
@@ -140,7 +216,7 @@ module.exports = class bitso extends Exchange {
'limits': limits,
'precision': precision,
'active': undefined,
});
}, fee));
}
return result;
}
@@ -216,16 +292,8 @@ module.exports = class bitso extends Exchange {
parseTrade (trade, market = undefined) {
const timestamp = this.parse8601 (this.safeString (trade, 'created_at'));
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (trade, 'book');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
}
if (market !== undefined) {
symbol = market['symbol'];
}
const marketId = this.safeString (trade, 'book');
const symbol = this.safeSymbol (marketId, market, '_');
const side = this.safeString2 (trade, 'side', 'maker_side');
let amount = this.safeFloat2 (trade, 'amount', 'major');
if (amount !== undefined) {
@@ -342,23 +410,8 @@ module.exports = class bitso extends Exchange {
const id = this.safeString (order, 'oid');
const side = this.safeString (order, 'side');
const status = this.parseOrderStatus (this.safeString (order, 'status'));
let symbol = undefined;
const marketId = this.safeString (order, 'book');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('_');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if (symbol === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
}
const symbol = this.safeSymbol (marketId, market, '_');
const orderType = this.safeString (order, 'type');
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
const price = this.safeFloat (order, 'price');
@@ -380,8 +433,10 @@ module.exports = class bitso extends Exchange {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': orderType,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': undefined,
'remaining': remaining,
@@ -456,10 +511,10 @@ module.exports = class bitso extends Exchange {
const response = await this.privateGetFundingDestination (this.extend (request, params));
let address = this.safeString (response['payload'], 'account_identifier');
let tag = undefined;
if (code === 'XRP') {
if (address.indexOf ('?dt=') >= 0) {
const parts = address.split ('?dt=');
address = parts[0];
tag = parts[1];
address = this.safeString (parts, 0);
tag = this.safeString (parts, 1);
}
this.checkAddress (address);
return {
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { AuthenticationError, ExchangeError, NotSupported, PermissionDenied, InvalidNonce, OrderNotFound, InsufficientFunds, InvalidAddress, InvalidOrder } = require ('./base/errors');
const { AuthenticationError, ExchangeError, NotSupported, PermissionDenied, InvalidNonce, OrderNotFound, InsufficientFunds, InvalidAddress, InvalidOrder, ArgumentsRequired, OnMaintenance } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -19,13 +19,27 @@ module.exports = class bitstamp extends Exchange {
'pro': true,
'has': {
'CORS': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchDepositAddress': true,
'fetchOrder': true,
'fetchOpenOrders': true,
'fetchMarkets': true,
'fetchCurrencies': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
'fetchTransactions': true,
'fetchWithdrawals': true,
'withdraw': true,
'fetchTradingFee': true,
'fetchTradingFees': true,
'fetchFundingFees': true,
'fetchFees': true,
'fetchLedger': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg',
@@ -37,6 +51,20 @@ module.exports = class bitstamp extends Exchange {
'www': 'https://www.bitstamp.net',
'doc': 'https://www.bitstamp.net/api',
},
'timeframes': {
'1m': '60',
'3m': '180',
'5m': '300',
'15m': '900',
'30m': '1800',
'1h': '3600',
'2h': '7200',
'4h': '14400',
'6h': '21600',
'12h': '43200',
'1d': '86400',
'1w': '259200',
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
@@ -45,6 +73,7 @@ module.exports = class bitstamp extends Exchange {
'api': {
'public': {
'get': [
'ohlc/{pair}/',
'order_book/{pair}/',
'ticker_hour/{pair}/',
'ticker/{pair}/',
@@ -76,6 +105,16 @@ module.exports = class bitstamp extends Exchange {
'eth_address/',
'xrp_withdrawal/',
'xrp_address/',
'xlm_withdrawal/',
'xlm_address/',
'pax_withdrawal/',
'pax_address/',
'link_withdrawal/',
'link_address/',
'usdc_withdrawal/',
'usdc_address/',
'omg_withdrawal/',
'omg_address/',
'transfer-to-main/',
'transfer-from-main/',
'withdrawal-requests/',
@@ -114,7 +153,13 @@ module.exports = class bitstamp extends Exchange {
[2000000, 0.13 / 100],
[4000000, 0.12 / 100],
[20000000, 0.11 / 100],
[20000001, 0.10 / 100],
[50000000, 0.10 / 100],
[100000000, 0.07 / 100],
[500000000, 0.05 / 100],
[2000000000, 0.03 / 100],
[6000000000, 0.01 / 100],
[10000000000, 0.005 / 100],
[10000000001, 0.0],
],
'maker': [
[0, 0.5 / 100],
@@ -127,7 +172,13 @@ module.exports = class bitstamp extends Exchange {
[2000000, 0.13 / 100],
[4000000, 0.12 / 100],
[20000000, 0.11 / 100],
[20000001, 0.10 / 100],
[50000000, 0.10 / 100],
[100000000, 0.07 / 100],
[500000000, 0.05 / 100],
[2000000000, 0.03 / 100],
[6000000000, 0.01 / 100],
[10000000000, 0.005 / 100],
[10000000001, 0.0],
],
},
},
@@ -135,13 +186,15 @@ module.exports = class bitstamp extends Exchange {
'tierBased': false,
'percentage': false,
'withdraw': {
'BTC': 0,
'BCH': 0,
'LTC': 0,
'ETH': 0,
'XRP': 0,
'BTC': 0.0005,
'BCH': 0.0001,
'LTC': 0.001,
'ETH': 0.001,
'XRP': 0.02,
'XLM': 0.005,
'PAX': 0.5,
'USD': 25,
'EUR': 0.90,
'EUR': 3.0,
},
'deposit': {
'BTC': 0,
@@ -149,7 +202,9 @@ module.exports = class bitstamp extends Exchange {
'LTC': 0,
'ETH': 0,
'XRP': 0,
'USD': 25,
'XLM': 0,
'PAX': 0,
'USD': 7.5,
'EUR': 0,
},
},
@@ -167,6 +222,7 @@ module.exports = class bitstamp extends Exchange {
'Please update your profile with your FATCA information, before using API.': PermissionDenied,
'Order not found': OrderNotFound,
'Price is more than 20% below market price.': InvalidOrder,
'Bitstamp.net is under scheduled maintenance.': OnMaintenance, // { "error": "Bitstamp.net is under scheduled maintenance. We'll be back soon." }
},
'broad': {
'Minimum order size is': InvalidOrder, // Minimum order size is 5.0 EUR.
@@ -178,7 +234,7 @@ module.exports = class bitstamp extends Exchange {
}
async fetchMarkets (params = {}) {
const response = await this.publicGetTradingPairsInfo (params);
const response = await this.fetchMarketsFromCache (params);
const result = [];
for (let i = 0; i < response.length; i++) {
const market = response[i];
@@ -229,6 +285,84 @@ module.exports = class bitstamp extends Exchange {
return result;
}
constructCurrencyObject (id, code, name, precision, minCost, originalPayload) {
let currencyType = 'crypto';
const description = this.describe ();
if (this.isFiat (code)) {
currencyType = 'fiat';
}
return {
'id': id,
'code': code,
'info': originalPayload, // the original payload
'type': currencyType,
'name': name,
'active': true,
'fee': this.safeFloat (description['fees']['funding']['withdraw'], code),
'precision': precision,
'limits': {
'amount': {
'min': Math.pow (10, -precision),
'max': undefined,
},
'price': {
'min': Math.pow (10, -precision),
'max': undefined,
},
'cost': {
'min': minCost,
'max': undefined,
},
'withdraw': {
'min': undefined,
'max': undefined,
},
},
};
}
async fetchMarketsFromCache (params = {}) {
// this method is now redundant
// currencies are now fetched before markets
const options = this.safeValue (this.options, 'fetchMarkets', {});
const timestamp = this.safeInteger (options, 'timestamp');
const expires = this.safeInteger (options, 'expires', 1000);
const now = this.milliseconds ();
if ((timestamp === undefined) || ((now - timestamp) > expires)) {
const response = await this.publicGetTradingPairsInfo (params);
this.options['fetchMarkets'] = this.extend (options, {
'response': response,
'timestamp': now,
});
}
return this.safeValue (this.options['fetchMarkets'], 'response');
}
async fetchCurrencies (params = {}) {
const response = await this.fetchMarketsFromCache (params);
const result = {};
for (let i = 0; i < response.length; i++) {
const market = response[i];
const name = this.safeString (market, 'name');
let [ base, quote ] = name.split ('/');
const baseId = base.toLowerCase ();
const quoteId = quote.toLowerCase ();
base = this.safeCurrencyCode (base);
quote = this.safeCurrencyCode (quote);
const description = this.safeString (market, 'description');
const [ baseDescription, quoteDescription ] = description.split (' / ');
const parts = market['minimum_order'].split (' ');
const cost = parts[0];
if (!(base in result)) {
result[base] = this.constructCurrencyObject (baseId, base, baseDescription, market['base_decimals'], undefined, market);
}
if (!(quote in result)) {
result[quote] = this.constructCurrencyObject (quoteId, quote, quoteDescription, market['counter_decimals'], parseFloat (cost), market);
}
}
return result;
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {
@@ -501,6 +635,16 @@ module.exports = class bitstamp extends Exchange {
};
}
parseTradingFee (balances, symbol) {
const market = this.market (symbol);
const tradeFee = this.safeFloat (balances, market['id'] + '_fee');
return {
'symbol': symbol,
'maker': tradeFee,
'taker': tradeFee,
};
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
@@ -530,6 +674,71 @@ module.exports = class bitstamp extends Exchange {
return this.parseTrades (response, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "high": "9064.77",
// "timestamp": "1593961440",
// "volume": "18.49436608",
// "low": "9040.87",
// "close": "9064.77",
// "open": "9040.87"
// }
//
return [
this.safeTimestamp (ohlcv, 'timestamp'),
this.safeFloat (ohlcv, 'open'),
this.safeFloat (ohlcv, 'high'),
this.safeFloat (ohlcv, 'low'),
this.safeFloat (ohlcv, 'close'),
this.safeFloat (ohlcv, 'volume'),
];
}
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'pair': market['id'],
'step': this.timeframes[timeframe],
};
const duration = this.parseTimeframe (timeframe);
if (limit === undefined) {
if (since === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument');
} else {
limit = 1000;
const start = parseInt (since / 1000);
request['start'] = start;
request['end'] = this.sum (start, limit * duration);
request['limit'] = limit;
}
} else {
if (since !== undefined) {
const start = parseInt (since / 1000);
request['start'] = start;
request['end'] = this.sum (start, limit * duration);
}
request['limit'] = Math.min (limit, 1000); // min 1, max 1000
}
const response = await this.publicGetOhlcPair (this.extend (request, params));
//
// {
// "data": {
// "pair": "BTC/USD",
// "ohlc": [
// {"high": "9064.77", "timestamp": "1593961440", "volume": "18.49436608", "low": "9040.87", "close": "9064.77", "open": "9040.87"},
// {"high": "9071.59", "timestamp": "1593961500", "volume": "3.48631711", "low": "9058.76", "close": "9061.07", "open": "9064.66"},
// {"high": "9067.33", "timestamp": "1593961560", "volume": "0.04142833", "low": "9061.94", "close": "9061.94", "open": "9067.33"},
// ],
// }
// }
//
const data = this.safeValue (response, 'data', {});
const ohlc = this.safeValue (data, 'ohlc', []);
return this.parseOHLCVs (ohlc, market, timeframe, since, limit);
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const balance = await this.privatePostBalance (params);
@@ -548,6 +757,80 @@ module.exports = class bitstamp extends Exchange {
return this.parseBalance (result);
}
async fetchTradingFee (symbol, params = {}) {
await this.loadMarkets ();
const request = {};
let method = 'privatePostBalance';
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['pair'] = market['id'];
method += 'Pair';
}
const balance = await this[method] (this.extend (request, params));
return {
'info': balance,
'symbol': symbol,
'maker': balance.fee,
'taker': balance.fee,
};
}
praseTradingFees (balance) {
const result = { 'info': balance };
const markets = Object.keys (this.markets);
for (let i = 0; i < markets.length; i++) {
const symbol = markets[i];
const fee = this.parseTradingFee (balance, symbol);
result[symbol] = fee;
}
return result;
}
async fetchTradingFees (params = {}) {
await this.loadMarkets ();
const balance = await this.privatePostBalance (params);
return this.praseTradingFees (balance);
}
parseFundingFees (balance) {
const withdraw = {};
const ids = Object.keys (balance);
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
if (id.indexOf ('_withdrawal_fee') >= 0) {
const currencyId = id.split ('_')[0];
const code = this.safeCurrencyCode (currencyId);
withdraw[code] = this.safeFloat (balance, id);
}
}
return {
'info': balance,
'withdraw': withdraw,
'deposit': {},
};
}
async fetchFundingFees (params = {}) {
await this.loadMarkets ();
const balance = await this.privatePostBalance (params);
return this.parseFundingFees (balance);
}
async fetchFees (params = {}) {
await this.loadMarkets ();
const balance = await this.privatePostBalance (params);
const tradingFees = this.praseTradingFees (balance);
delete tradingFees['info'];
const fundingFees = this.parseFundingFees (balance);
delete fundingFees['info'];
return {
'info': balance,
'trading': tradingFees,
'funding': fundingFees,
};
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
@@ -558,6 +841,8 @@ module.exports = class bitstamp extends Exchange {
};
if (type === 'market') {
method += 'Market';
} else if (type === 'instant') {
method += 'Instant';
} else {
request['price'] = this.priceToPrecision (symbol, price);
}
@@ -687,6 +972,8 @@ module.exports = class bitstamp extends Exchange {
const request = {};
if (since !== undefined) {
request['timedelta'] = this.milliseconds () - since;
} else {
request['timedelta'] = 50000000; // use max bitstamp approved value
}
const response = await this.privatePostWithdrawalRequests (this.extend (request, params));
//
@@ -974,8 +1261,10 @@ module.exports = class bitstamp extends Exchange {
'status': status,
'symbol': symbol,
'type': undefined,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -987,6 +1276,124 @@ module.exports = class bitstamp extends Exchange {
};
}
parseLedgerEntryType (type) {
const types = {
'0': 'transaction',
'1': 'transaction',
'2': 'trade',
'14': 'transfer',
};
return this.safeString (types, type, type);
}
parseLedgerEntry (item, currency = undefined) {
//
// [
// {
// "fee": "0.00000000",
// "btc_usd": "0.00",
// "id": 1234567894,
// "usd": 0,
// "btc": 0,
// "datetime": "2018-09-08 09:00:31",
// "type": "1",
// "xrp": "-20.00000000",
// "eur": 0,
// },
// {
// "fee": "0.00000000",
// "btc_usd": "0.00",
// "id": 1134567891,
// "usd": 0,
// "btc": 0,
// "datetime": "2018-09-07 18:47:52",
// "type": "0",
// "xrp": "20.00000000",
// "eur": 0,
// },
// ]
//
const type = this.parseLedgerEntryType (this.safeString (item, 'type'));
if (type === 'trade') {
const parsedTrade = this.parseTrade (item);
let market = undefined;
const keys = Object.keys (item);
for (let i = 0; i < keys.length; i++) {
if (keys[i].indexOf ('_') >= 0) {
const marketId = keys[i].replace ('_', '');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
}
}
// if the market is still not defined
// try to deduce it from used keys
if (market === undefined) {
market = this.getMarketFromTrade (item);
}
const direction = parsedTrade['side'] === 'buy' ? 'in' : 'out';
return {
'id': parsedTrade['id'],
'info': item,
'timestamp': parsedTrade['timestamp'],
'datetime': parsedTrade['datetime'],
'direction': direction,
'account': undefined,
'referenceId': parsedTrade['order'],
'referenceAccount': undefined,
'type': type,
'currency': market['base'],
'amount': parsedTrade['amount'],
'before': undefined,
'after': undefined,
'status': 'ok',
'fee': parsedTrade['fee'],
};
} else {
const parsedTransaction = this.parseTransaction (item);
let direction = undefined;
if ('amount' in item) {
const amount = this.safeFloat (item, 'amount');
direction = amount > 0 ? 'in' : 'out';
} else if (('currency' in parsedTransaction) && parsedTransaction['currency'] !== undefined) {
const currencyId = this.currencyId (parsedTransaction['currency']);
const amount = this.safeFloat (item, currencyId);
direction = amount > 0 ? 'in' : 'out';
}
return {
'id': parsedTransaction['id'],
'info': item,
'timestamp': parsedTransaction['timestamp'],
'datetime': parsedTransaction['datetime'],
'direction': direction,
'account': undefined,
'referenceId': parsedTransaction['txid'],
'referenceAccount': undefined,
'type': type,
'currency': parsedTransaction['currency'],
'amount': parsedTransaction['amount'],
'before': undefined,
'after': undefined,
'status': parsedTransaction['status'],
'fee': parsedTransaction['fee'],
};
}
}
async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.privatePostUserTransactions (this.extend (request, params));
let currency = undefined;
if (code !== undefined) {
currency = this.currency (code);
}
return this.parseLedger (response, currency, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
let market = undefined;
await this.loadMarkets ();
@@ -1005,18 +1412,10 @@ module.exports = class bitstamp extends Exchange {
// }
// ]
//
const result = [];
for (let i = 0; i < response.length; i++) {
const order = this.parseOrder (response[i], market);
result.push (this.extend (order, {
'status': 'open',
'type': 'limit',
}));
}
if (symbol === undefined) {
return this.filterBySinceLimit (result, since, limit);
}
return this.filterBySymbolSinceLimit (result, symbol, since, limit);
return this.parseOrders (response, market, since, limit, {
'status': 'open',
'type': 'limit',
});
}
getCurrencyName (code) {
@@ -1027,18 +1426,12 @@ module.exports = class bitstamp extends Exchange {
}
isFiat (code) {
if (code === 'USD') {
return true;
}
if (code === 'EUR') {
return true;
}
return false;
return code === 'USD' || code === 'EUR' || code === 'GBP';
}
async fetchDepositAddress (code, params = {}) {
if (this.isFiat (code)) {
throw new NotSupported (this.id + ' fiat fetchDepositAddress() for ' + code + ' is not implemented yet');
throw new NotSupported (this.id + ' fiat fetchDepositAddress() for ' + code + ' is not supported!');
}
const name = this.getCurrencyName (code);
const v1 = (code === 'BTC');
@@ -1051,7 +1444,7 @@ module.exports = class bitstamp extends Exchange {
response = JSON.parse (response);
}
const address = v1 ? response : this.safeString (response, 'address');
const tag = v1 ? undefined : this.safeString (response, 'destination_tag');
const tag = v1 ? undefined : this.safeString2 (response, 'memo_id', 'destination_tag');
this.checkAddress (address);
return {
'currency': code,
@@ -1062,22 +1455,30 @@ module.exports = class bitstamp extends Exchange {
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
// For fiat withdrawals please provide all required additional parameters in the 'params'
// Check https://www.bitstamp.net/api/ under 'Open bank withdrawal' for list and description.
await this.loadMarkets ();
this.checkAddress (address);
if (this.isFiat (code)) {
throw new NotSupported (this.id + ' fiat withdraw() for ' + code + ' is not implemented yet');
}
const name = this.getCurrencyName (code);
const request = {
'amount': amount,
'address': address,
};
const v1 = (code === 'BTC');
let method = v1 ? 'v1' : 'private'; // v1 or v2
method += 'Post' + this.capitalize (name) + 'Withdrawal';
if (code === 'XRP') {
if (tag !== undefined) {
request['destination_tag'] = tag;
let method = undefined;
if (!this.isFiat (code)) {
const name = this.getCurrencyName (code);
const v1 = (code === 'BTC');
method = v1 ? 'v1' : 'private'; // v1 or v2
method += 'Post' + this.capitalize (name) + 'Withdrawal';
if (code === 'XRP') {
if (tag !== undefined) {
request['destination_tag'] = tag;
}
}
request['address'] = address;
} else {
method = 'privatePostWithdrawalOpen';
const currency = this.currency (code);
request['iban'] = address;
request['account_currency'] = currency['id'];
}
const response = await this[method] (this.extend (request, params));
return {
@@ -16,7 +16,15 @@ module.exports = class bitstamp1 extends Exchange {
'rateLimit': 1000,
'version': 'v1',
'has': {
'cancelOrder': true,
'CORS': true,
'createOrder': true,
'fetchBalance': true,
'fetchMyTrades': true,
'fetchOrder': false,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg',
@@ -59,18 +67,18 @@ module.exports = class bitstamp1 extends Exchange {
},
},
'markets': {
'BTC/USD': { 'id': 'btcusd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'btc', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
'BTC/EUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
'EUR/USD': { 'id': 'eurusd', 'symbol': 'EUR/USD', 'base': 'EUR', 'quote': 'USD', 'baseId': 'eur', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
'XRP/USD': { 'id': 'xrpusd', 'symbol': 'XRP/USD', 'base': 'XRP', 'quote': 'USD', 'baseId': 'xrp', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
'XRP/EUR': { 'id': 'xrpeur', 'symbol': 'XRP/EUR', 'base': 'XRP', 'quote': 'EUR', 'baseId': 'xrp', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc', 'maker': 0.0025, 'taker': 0.0025 },
'LTC/USD': { 'id': 'ltcusd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'ltc', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
'LTC/EUR': { 'id': 'ltceur', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'ltc', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.0025, 'taker': 0.0025 },
'ETH/USD': { 'id': 'ethusd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'eth', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
'ETH/EUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'eth', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.0025, 'taker': 0.0025 },
'BTC/USD': { 'id': 'btcusd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'btc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 },
'BTC/EUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 },
'EUR/USD': { 'id': 'eurusd', 'symbol': 'EUR/USD', 'base': 'EUR', 'quote': 'USD', 'baseId': 'eur', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 },
'XRP/USD': { 'id': 'xrpusd', 'symbol': 'XRP/USD', 'base': 'XRP', 'quote': 'USD', 'baseId': 'xrp', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 },
'XRP/EUR': { 'id': 'xrpeur', 'symbol': 'XRP/EUR', 'base': 'XRP', 'quote': 'EUR', 'baseId': 'xrp', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 },
'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 },
'LTC/USD': { 'id': 'ltcusd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'ltc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 },
'LTC/EUR': { 'id': 'ltceur', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'ltc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 },
'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 },
'ETH/USD': { 'id': 'ethusd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'eth', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 },
'ETH/EUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'eth', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 },
'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 },
},
});
}
File diff suppressed because it is too large. Load diff
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, PermissionDenied, InvalidOrder, AuthenticationError, InsufficientFunds, OrderNotFound, DDoSProtection } = require ('./base/errors');
const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, PermissionDenied, InvalidOrder, AuthenticationError, InsufficientFunds, OrderNotFound, DDoSProtection, OnMaintenance, RateLimitExceeded } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -17,16 +17,26 @@ module.exports = class bitz extends Exchange {
'version': 'v2',
'userAgent': this.userAgents['chrome'],
'has': {
'fetchTickers': true,
'cancelOrder': true,
'cancelOrders': true,
'createOrder': true,
'createMarketOrder': false,
'fetchBalance': true,
'fetchDeposits': true,
'fetchClosedOrders': true,
'fetchMarkets': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchOrders': true,
'fetchOrder': true,
'createMarketOrder': false,
'fetchDeposits': true,
'fetchWithdrawals': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchTransactions': false,
'fetchWithdrawals': true,
'withdraw': true,
},
'timeframes': {
'1m': '1min',
@@ -42,7 +52,7 @@ module.exports = class bitz extends Exchange {
},
'hostname': 'apiv2.bitz.com',
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/35862606-4f554f14-0b5d-11e8-957d-35058c504b6f.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87443304-fec5e000-c5fd-11ea-98f8-ba8e67f7eaff.jpg',
'api': {
'market': 'https://{hostname}',
'trade': 'https://{hostname}',
@@ -62,6 +72,7 @@ module.exports = class bitz extends Exchange {
'tickerall',
'kline',
'symbolList',
'getServerTime',
'currencyRate',
'currencyCoinRate',
'coinRate',
@@ -72,6 +83,7 @@ module.exports = class bitz extends Exchange {
'addEntrustSheet',
'cancelEntrustSheet',
'cancelAllEntrustSheet',
'coinOut', // withdraw
'getUserHistoryEntrustSheet', // closed orders
'getUserNowEntrustSheet', // open orders
'getEntrustSheetInfo', // order
@@ -176,7 +188,9 @@ module.exports = class bitz extends Exchange {
'-109': AuthenticationError, // Invalid scretKey
'-110': DDoSProtection, // The number of access requests exceeded
'-111': PermissionDenied, // Current IP is not in the range of trusted IP
'-112': ExchangeNotAvailable, // Service is under maintenance
'-112': OnMaintenance, // Service is under maintenance
'-114': RateLimitExceeded, // The number of daily requests has reached the limit
'-117': AuthenticationError, // The apikey expires
'-100015': AuthenticationError, // Trade password error
'-100044': ExchangeError, // Fail to request data
'-100101': ExchangeError, // Invalid symbol
@@ -350,14 +364,8 @@ module.exports = class bitz extends Exchange {
// krw: "318655.82" }
//
const timestamp = undefined;
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (ticker, 'symbol');
market = this.safeValue (this.markets_by_id, marketId);
}
if (market !== undefined) {
symbol = market['symbol'];
}
const marketId = this.safeString (ticker, 'symbol');
const symbol = this.safeSymbol (marketId, market, '_');
const last = this.safeFloat (ticker, 'now');
const open = this.safeFloat (ticker, 'open');
let change = undefined;
@@ -510,7 +518,22 @@ module.exports = class bitz extends Exchange {
});
}
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTime (params = {}) {
const response = await this.marketGetGetServerTime (params);
//
// {
// "status":200,
// "msg":"",
// "data":[],
// "time":1555490875,
// "microtime":"0.35994200 1555490875",
// "source":"api"
// }
//
return this.safeTimestamp (response, 'time');
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -615,15 +638,17 @@ module.exports = class bitz extends Exchange {
return this.parseTrades (response['data'], market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// { time: "1535973420000",
// open: "0.03975084",
// high: "0.03975084",
// low: "0.03967700",
// close: "0.03967700",
// volume: "12.4733",
// datetime: "2018-09-03 19:17:00" }
// {
// time: "1535973420000",
// open: "0.03975084",
// high: "0.03975084",
// low: "0.03967700",
// close: "0.03967700",
// volume: "12.4733",
// datetime: "2018-09-03 19:17:00"
// }
//
return [
this.safeInteger (ohlcv, 'time'),
@@ -655,35 +680,27 @@ module.exports = class bitz extends Exchange {
}
const response = await this.marketGetKline (this.extend (request, params));
//
// { status: 200,
// msg: "",
// data: { bars: [ { time: "1535973420000",
// open: "0.03975084",
// high: "0.03975084",
// low: "0.03967700",
// close: "0.03967700",
// volume: "12.4733",
// datetime: "2018-09-03 19:17:00" },
// { time: "1535955480000",
// open: "0.04009900",
// high: "0.04016745",
// low: "0.04009900",
// close: "0.04012074",
// volume: "74.4803",
// datetime: "2018-09-03 14:18:00" } ],
// resolution: "1min",
// symbol: "eth_btc",
// from: "1535973420000",
// to: "1535955480000",
// size: 300 },
// time: 1535973435,
// microtime: "0.56462100 1535973435",
// source: "api" }
// {
// status: 200,
// msg: "",
// data: {
// bars: [
// { time: "1535973420000", open: "0.03975084", high: "0.03975084", low: "0.03967700", close: "0.03967700", volume: "12.4733", datetime: "2018-09-03 19:17:00" },
// { time: "1535955480000", open: "0.04009900", high: "0.04016745", low: "0.04009900", close: "0.04012074", volume: "74.4803", datetime: "2018-09-03 14:18:00" },
// ],
// resolution: "1min",
// symbol: "eth_btc",
// from: "1535973420000",
// to: "1535955480000",
// size: 300
// },
// time: 1535973435,
// microtime: "0.56462100 1535973435",
// source: "api"
// }
//
const bars = this.safeValue (response['data'], 'bars', undefined);
if (bars === undefined) {
return [];
}
const data = this.safeValue (response, 'data', {});
const bars = this.safeValue (data, 'bars', []);
return this.parseOHLCVs (bars, market, timeframe, since, limit);
}
@@ -761,8 +778,10 @@ module.exports = class bitz extends Exchange {
'status': status,
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -1079,6 +1098,16 @@ module.exports = class bitz extends Exchange {
// "memo":""
// }
//
// withdraw
//
// {
// "id":397574,
// "email":"***@email.com",
// "coin":"usdt",
// "network_fee":"",
// "eid":23112
// }
//
let timestamp = this.safeInteger (transaction, 'updated');
if (timestamp === 0) {
timestamp = undefined;
@@ -1087,6 +1116,14 @@ module.exports = class bitz extends Exchange {
const code = this.safeCurrencyCode (currencyId, currency);
const type = this.safeStringLower (transaction, 'type');
const status = this.parseTransactionStatus (this.safeString (transaction, 'status'));
let fee = undefined;
const feeCost = this.safeFloat (transaction, 'network_fee');
if (feeCost !== undefined) {
fee = {
'cost': feeCost,
'code': code,
};
}
return {
'id': this.safeString (transaction, 'id'),
'txid': this.safeString (transaction, 'txid'),
@@ -1099,7 +1136,7 @@ module.exports = class bitz extends Exchange {
'currency': code,
'status': status,
'updated': timestamp,
'fee': undefined,
'fee': fee,
'info': transaction,
};
}
@@ -1153,6 +1190,40 @@ module.exports = class bitz extends Exchange {
return this.parseTransactionsByType (type, transactions, code, since, limit);
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
this.checkAddress (address);
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
'coin': currency['id'],
'number': this.currencyToPrecision (code, amount),
'address': address,
// 'type': 'erc20', // omni, trc20, optional
};
if (tag !== undefined) {
request['memo'] = tag;
}
const response = await this.tradePostCoinOut (this.extend (request, params));
//
// {
// "status":200,
// "msg":"",
// "data":{
// "id":397574,
// "email":"***@email.com",
// "coin":"usdt",
// "network_fee":"",
// "eid":23112
// },
// "time":1552641646,
// "microtime":"0.70304500 1552641646",
// "source":"api"
// }
//
const data = this.safeValue (response, 'data', {});
return this.parseTransaction (data, currency);
}
nonce () {
const currentTimestamp = this.seconds ();
if (currentTimestamp > this.options['lastNonceTimestamp']) {
@@ -17,14 +17,17 @@ module.exports = class bl3p extends Exchange {
'comment': 'An exchange market by BitonicNL',
'has': {
'CORS': false,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/28501752-60c21b82-6feb-11e7-818b-055ee6d0e754.jpg',
'api': 'https://api.bl3p.eu',
'www': [
'https://bl3p.eu',
'https://bitonic.nl',
],
'www': 'https://bl3p.eu', // 'https://bitonic.nl'
'doc': [
'https://github.com/BitonicNL/bl3p-api/tree/master/docs',
'https://bl3p.eu/api',
@@ -225,7 +228,7 @@ module.exports = class bl3p extends Exchange {
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Rest-Key': this.apiKey,
'Rest-Sign': this.decode (signature),
'Rest-Sign': signature,
};
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
@@ -15,36 +15,36 @@ module.exports = class bleutrade extends Exchange {
'rateLimit': 1000,
'certified': false,
'has': {
'cancelOrder': true,
'CORS': true,
'cancelOrder': false, // todo
'createLimitOrder': false, // todo
'createMarketOrder': false, // todo
'createOrder': false, // todo
'editOrder': false, // todo
'withdraw': false, // todo
'fetchTrades': false,
'fetchTickers': true,
'fetchTicker': true,
'fetchOrders': false,
'createLimitOrder': false,
'createMarketOrder': false,
'createOrder': true,
'editOrder': false,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchOpenOrders': true,
'fetchWithdrawals': true,
'fetchOrderTrades': false,
'fetchLedger': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchLedger': true,
'fetchMarkets': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrderBook': true,
'fetchOrders': false,
'fetchOrderTrades': false,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': false,
'fetchWithdrawals': true,
'withdraw': false,
},
'timeframes': {
'15m': '15m',
'20m': '20m',
'30m': '30m',
'1h': '1h',
'2h': '2h',
'3h': '3h',
'4h': '4h',
'6h': '6h',
'8h': '8h',
'12h': '12h',
'1d': '1d',
'1w': '1w',
},
'hostname': 'bleutrade.com',
'urls': {
@@ -53,7 +53,7 @@ module.exports = class bleutrade extends Exchange {
'v3Private': 'https://{hostname}/api/v3/private',
'v3Public': 'https://{hostname}/api/v3/public',
},
'www': ['https://bleutrade.com'],
'www': 'https://bleutrade.com',
'doc': [
'https://app.swaggerhub.com/apis-docs/bleu/white-label/3.0.0',
],
@@ -105,6 +105,7 @@ module.exports = class bleutrade extends Exchange {
'exact': {
'ERR_INSUFICIENT_BALANCE': InsufficientFunds,
'ERR_LOW_VOLUME': BadRequest,
'Invalid form': BadRequest,
},
'broad': {
'Order is not open': InvalidOrder,
@@ -121,7 +122,6 @@ module.exports = class bleutrade extends Exchange {
},
'options': {
'parseOrderStatus': true,
'symbolSeparator': '_',
},
});
// undocumented api calls
@@ -253,7 +253,7 @@ module.exports = class bleutrade extends Exchange {
'market': market['id'],
};
const response = await this.v3PublicGetGetmarketsummary (this.extend (request, params));
const ticker = response['result'][0];
const ticker = this.safeValue (response, 'result', {});
return this.parseTicker (ticker, market);
}
@@ -290,18 +290,8 @@ module.exports = class bleutrade extends Exchange {
// MarketCurrency: 'Litecoin',
// BaseCurrency: 'Tether' }
const timestamp = this.parse8601 (this.safeString (ticker, 'TimeStamp'));
let symbol = undefined;
const marketId = this.safeString (ticker, 'MarketName');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
symbol = this.parseSymbol (marketId);
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market, '_');
const previous = this.safeFloat (ticker, 'PrevDay');
const last = this.safeFloat (ticker, 'Last');
let change = undefined;
@@ -338,10 +328,9 @@ module.exports = class bleutrade extends Exchange {
};
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1d', since = undefined, limit = undefined) {
const timestamp = this.parse8601 (ohlcv['TimeStamp'] + '+00:00');
parseOHLCV (ohlcv, market = undefined) {
return [
timestamp,
this.parse8601 (ohlcv['TimeStamp'] + '+00:00'),
this.safeFloat (ohlcv, 'Open'),
this.safeFloat (ohlcv, 'High'),
this.safeFloat (ohlcv, 'Low'),
@@ -359,7 +348,8 @@ module.exports = class bleutrade extends Exchange {
'count': limit,
};
const response = await this.v3PublicGetGetcandles (this.extend (request, params));
return this.parseOHLCVs (response['result'], market, timeframe, since, limit);
const result = this.safeValue (response, 'result', []);
return this.parseOHLCVs (result, market, timeframe, since, limit);
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
@@ -411,13 +401,6 @@ module.exports = class bleutrade extends Exchange {
return this.parseOrders (items, market, since, limit);
}
parseSymbol (id) {
let [ base, quote ] = id.split (this.options['symbolSeparator']);
base = this.safeCurrencyCode (base);
quote = this.safeCurrencyCode (quote);
return base + '/' + quote;
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const response = await this.v3PrivatePostGetbalances (params);
@@ -643,20 +626,8 @@ module.exports = class bleutrade extends Exchange {
// Comments: { String: '', Valid: true }
const side = this.safeString (order, 'Type').toLowerCase ();
const status = this.parseOrderStatus (this.safeString (order, 'Status'));
let symbol = undefined;
const marketId = this.safeString (order, 'Exchange');
if (marketId === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
} else {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
symbol = this.parseSymbol (marketId);
}
}
const symbol = this.safeSymbol (marketId, market, '_');
let timestamp = undefined;
if ('Created' in order) {
timestamp = this.parse8601 (order['Created'] + '+00:00');
@@ -690,8 +661,10 @@ module.exports = class bleutrade extends Exchange {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'average': average,
'amount': amount,
@@ -818,22 +791,23 @@ module.exports = class bleutrade extends Exchange {
// {"success":false,"message":"Erro: Order is not open.","result":""} <-- 'error' is spelt wrong
// {"success":false,"message":"Error: Very low volume.","result":"ERR_LOW_VOLUME"}
// {"success":false,"message":"Error: Insuficient Balance","result":"ERR_INSUFICIENT_BALANCE"}
// {"success":false,"message":"Invalid form","result":null}
//
if (body[0] === '{') {
const success = this.safeValue (response, 'success');
if (success === undefined) {
throw new ExchangeError (this.id + ': malformed response: ' + this.json (response));
}
if (!success) {
const feedback = this.id + ' ' + body;
const errorCode = this.safeString (response, 'result');
const success = this.safeValue (response, 'success');
if (success === undefined) {
throw new ExchangeError (this.id + ': malformed response: ' + this.json (response));
}
if (!success) {
const feedback = this.id + ' ' + body;
const errorCode = this.safeString (response, 'result');
if (errorCode !== undefined) {
this.throwBroadlyMatchedException (this.exceptions['broad'], errorCode, feedback);
this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback);
const errorMessage = this.safeString (response, 'message');
this.throwBroadlyMatchedException (this.exceptions['broad'], errorMessage, feedback);
this.throwExactlyMatchedException (this.exceptions['exact'], errorMessage, feedback);
throw new ExchangeError (feedback);
}
const errorMessage = this.safeString (response, 'message');
this.throwBroadlyMatchedException (this.exceptions['broad'], errorMessage, feedback);
this.throwExactlyMatchedException (this.exceptions['exact'], errorMessage, feedback);
throw new ExchangeError (feedback);
}
}
};
@@ -15,12 +15,19 @@ module.exports = class braziliex extends Exchange {
'countries': [ 'BR' ],
'rateLimit': 1000,
'has': {
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchCurrencies': true,
'fetchTickers': true,
'fetchOpenOrders': true,
'fetchMyTrades': true,
'fetchDepositAddress': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/34703593-c4498674-f504-11e7-8d14-ff8e44fb78c1.jpg',
@@ -337,11 +344,11 @@ module.exports = class braziliex extends Exchange {
const ids = Object.keys (response);
for (let i = 0; i < ids.length; i++) {
const marketId = ids[i];
const market = this.markets_by_id[marketId];
const market = this.safeMarket (marketId);
const symbol = market['symbol'];
result[symbol] = this.parseTicker (response[marketId], market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -423,16 +430,8 @@ module.exports = class braziliex extends Exchange {
// "date":"2017-03-12 15:13:33"
// }
//
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (order, 'market');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
}
if (market !== undefined) {
symbol = market['symbol'];
}
const marketId = this.safeString (order, 'market');
const symbol = this.safeSymbol (marketId, market, '_');
let timestamp = this.safeInteger (order, 'timestamp');
if (timestamp === undefined) {
timestamp = this.parse8601 (this.safeString (order, 'date'));
@@ -450,6 +449,7 @@ module.exports = class braziliex extends Exchange {
const id = this.safeString (order, 'order_number');
const fee = this.safeValue (order, 'fee'); // propagated from createOrder
const status = (filledPercentage === 1.0) ? 'closed' : 'open';
const side = this.safeString (order, 'type');
return {
'id': id,
'clientOrderId': undefined,
@@ -459,8 +459,10 @@ module.exports = class braziliex extends Exchange {
'status': status,
'symbol': symbol,
'type': 'limit',
'side': order['type'],
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -484,30 +486,49 @@ module.exports = class braziliex extends Exchange {
'amount': amount,
};
const response = await this[method] (this.extend (request, params));
//
// sell
//
// {
// "success":1,
// "message":" ##RESERVED FOR ORDER / SELL / XMR_BTC / AMOUNT: 0.01 XMR / PRICE: 0.017 BTC / TOTAL: 0.00017000 BTC / FEE: 0.00002500 XMR ",
// "order_number":"590b962ba5b98335965fa0a8"
// }
//
// buy
//
// {
// "success":1,
// "message":" ##RESERVED FOR ORDER / BUY / XMR_BTC / AMOUNT: 0.005 XMR / PRICE: 0.017 BTC / TOTAL: 0.00008500 BTC / FEE: 0.00000021 BTC ",
// "order_number":"590b962ba5b98335965fa0c0"
// }
//
const success = this.safeInteger (response, 'success');
if (success !== 1) {
throw new InvalidOrder (this.id + ' ' + this.json (response));
}
let parts = response['message'].split (' / ');
const message = this.safeString (response, 'message');
let parts = message.split (' / ');
parts = parts.slice (1);
const feeParts = parts[5].split (' ');
const amountParts = parts[2].split (' ');
const priceParts = parts[3].split (' ');
const totalParts = parts[4].split (' ');
const order = this.parseOrder ({
'timestamp': this.milliseconds (),
'order_number': response['order_number'],
'type': parts[0].toLowerCase (),
'type': this.safeStringLower (parts, 0),
'market': parts[0].toLowerCase (),
'amount': parts[2].split (' ')[1],
'price': parts[3].split (' ')[1],
'total': parts[4].split (' ')[1],
'amount': this.safeString (amountParts, 1),
'price': this.safeString (priceParts, 1),
'total': this.safeString (totalParts, 1),
'fee': {
'cost': parseFloat (feeParts[1]),
'currency': feeParts[2],
'cost': this.safeFloat (feeParts, 1),
'currency': this.safeString (feeParts, 2),
},
'progress': '0.0',
'info': response,
}, market);
const id = order['id'];
this.orders[id] = order;
return order;
}
@@ -542,7 +563,8 @@ module.exports = class braziliex extends Exchange {
'market': market['id'],
};
const response = await this.privatePostOpenOrders (this.extend (request, params));
return this.parseOrders (response['order_open'], market, since, limit);
const orders = this.safeValue (response, 'order_open', []);
return this.parseOrders (orders, market, since, limit);
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -552,7 +574,8 @@ module.exports = class braziliex extends Exchange {
'market': market['id'],
};
const response = await this.privatePostTradeHistory (this.extend (request, params));
return this.parseTrades (response['trade_history'], market, since, limit);
const trades = this.safeValue (response, 'trade_history', []);
return this.parseTrades (trades, market, since, limit);
}
async fetchDepositAddress (code, params = {}) {
@@ -15,12 +15,19 @@ module.exports = class btcalpha extends Exchange {
'countries': [ 'US' ],
'version': 'v1',
'has': {
'fetchTicker': false,
'fetchOHLCV': true,
'fetchOrders': true,
'fetchOpenOrders': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': false,
'fetchTrades': true,
},
'timeframes': {
'1m': '1',
@@ -234,7 +241,17 @@ module.exports = class btcalpha extends Exchange {
return this.parseTrades (trades, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '5m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "time":1591296000,
// "open":0.024746,
// "close":0.024728,
// "low":0.024728,
// "high":0.024753,
// "volume":16.624
// }
//
return [
this.safeTimestamp (ohlcv, 'time'),
this.safeFloat (ohlcv, 'open'),
@@ -259,6 +276,13 @@ module.exports = class btcalpha extends Exchange {
request['since'] = parseInt (since / 1000);
}
const response = await this.publicGetChartsPairTypeChart (this.extend (request, params));
//
// [
// {"time":1591296000,"open":0.024746,"close":0.024728,"low":0.024728,"high":0.024753,"volume":16.624},
// {"time":1591295700,"open":0.024718,"close":0.02475,"low":0.024711,"high":0.02475,"volume":31.645},
// {"time":1591295400,"open":0.024721,"close":0.024717,"low":0.024711,"high":0.02473,"volume":65.071}
// ]
//
return this.parseOHLCVs (response, market, timeframe, since, limit);
}
@@ -323,8 +347,10 @@ module.exports = class btcalpha extends Exchange {
'status': status,
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': undefined,
'amount': amount,
'filled': filled,
@@ -16,17 +16,23 @@ module.exports = class btcbox extends Exchange {
'rateLimit': 1000,
'version': 'v1',
'has': {
'cancelOrder': true,
'CORS': false,
'fetchOrder': true,
'fetchOrders': true,
'createOrder': true,
'fetchBalance': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTickers': false,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/31275803-4df755a8-aaa1-11e7-9abb-11ec2fad9f2d.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87327317-98c55400-c53c-11ea-9a11-81f7d951cc74.jpg',
'api': 'https://www.btcbox.co.jp/api',
'www': 'https://www.btcbox.co.jp/',
'doc': 'https://www.btcbox.co.jp/help/asm',
'doc': 'https://blog.btcbox.jp/en/archives/8762',
'fees': 'https://support.btcbox.co.jp/hc/en-us/articles/360001235694-Fees-introduction',
},
'api': {
@@ -299,9 +305,11 @@ module.exports = class btcbox extends Exchange {
'filled': filled,
'side': side,
'type': undefined,
'timeInForce': undefined,
'status': status,
'symbol': symbol,
'price': price,
'stopPrice': undefined,
'cost': cost,
'trades': trades,
'fee': undefined,
File diff suppressed because it is too large. Load diff
@@ -15,9 +15,15 @@ module.exports = class btctradeua extends Exchange {
'countries': [ 'UA' ], // Ukraine,
'rateLimit': 3000,
'has': {
'cancelOrder': true,
'CORS': false,
'createMarketOrder': false,
'createOrder': true,
'fetchBalance': true,
'fetchOpenOrders': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
'signIn': true,
},
'urls': {
@@ -328,8 +334,10 @@ module.exports = class btctradeua extends Exchange {
'status': 'open',
'symbol': symbol,
'type': undefined,
'timeInForce': undefined,
'side': this.safeString (order, 'type'),
'price': this.safeFloat (order, 'price'),
'stopPrice': undefined,
'amount': this.safeFloat (order, 'amnt_trade'),
'filled': 0,
'remaining': this.safeFloat (order, 'amnt_trade'),
@@ -15,15 +15,22 @@ module.exports = class btcturk extends Exchange {
'countries': [ 'TR' ], // Turkey
'rateLimit': 1000,
'has': {
'cancelOrder': true,
'CORS': true,
'fetchTickers': true,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchOHLCV': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
},
'timeframes': {
'1d': '1d',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27992709-18e15646-64a3-11e7-9fa2-b0950ec7712f.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87153926-efbef500-c2c0-11ea-9842-05b63612c4b9.jpg',
'api': 'https://www.btcturk.com/api',
'www': 'https://www.btcturk.com',
'doc': 'https://github.com/BTCTrader/broker-api-docs',
@@ -184,7 +191,7 @@ module.exports = class btcturk extends Exchange {
}
result[symbol] = this.parseTicker (ticker, market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -237,10 +244,9 @@ module.exports = class btcturk extends Exchange {
return this.parseTrades (response, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1d', since = undefined, limit = undefined) {
const timestamp = this.parse8601 (this.safeString (ohlcv, 'Time'));
parseOHLCV (ohlcv, market = undefined) {
return [
timestamp,
this.parse8601 (this.safeString (ohlcv, 'Time')),
this.safeFloat (ohlcv, 'Open'),
this.safeFloat (ohlcv, 'High'),
this.safeFloat (ohlcv, 'Low'),
@@ -312,7 +318,7 @@ module.exports = class btcturk extends Exchange {
headers = {
'X-PCK': this.apiKey,
'X-Stamp': nonce,
'X-Signature': this.stringToBase64 (this.hmac (this.encode (auth), secret, 'sha256', 'binary')),
'X-Signature': this.hmac (this.encode (auth), secret, 'sha256', 'base64'),
'Content-Type': 'application/x-www-form-urlencoded',
};
}
@@ -16,19 +16,25 @@ module.exports = class buda extends Exchange {
'rateLimit': 1000,
'version': 'v2',
'has': {
'cancelOrder': true,
'CORS': false,
'createDepositAddress': true,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchFundingFees': true,
'fetchMarkets': true,
'fetchMyTrades': false,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTrades': true,
'fetchTicker': true,
'fetchWithdrawals': true,
'withdraw': true,
},
@@ -533,16 +539,8 @@ module.exports = class buda extends Exchange {
parseOrder (order, market = undefined) {
const id = this.safeString (order, 'id');
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
let symbol = undefined;
if (market === undefined) {
const marketId = order['market_id'];
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
}
if (market !== undefined) {
symbol = market['symbol'];
}
const marketId = this.safeString (order, 'market_id');
const symbol = this.safeSymbol (marketId, market);
const type = this.safeString (order, 'price_type');
const side = this.safeStringLower (order, 'type');
const status = this.parseOrderStatus (this.safeString (order, 'state'));
@@ -570,8 +568,10 @@ module.exports = class buda extends Exchange {
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -755,7 +755,7 @@ module.exports = class buda extends Exchange {
const nonce = this.nonce ().toString ();
const components = [ method, '/api/' + this.version + '/' + request ];
if (body) {
const base64Body = this.stringToBase64 (this.encode (body));
const base64Body = this.stringToBase64 (body);
components.push (this.decode (base64Body));
}
components.push (nonce);
@@ -70,6 +70,7 @@ module.exports = class bw extends Exchange {
'www': 'https://www.bw.com',
'doc': 'https://github.com/bw-exchange/api_docs_en/wiki',
'fees': 'https://www.bw.com/feesRate',
'referral': 'https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0',
},
'requiredCredentials': {
'apiKey': true,
@@ -332,16 +333,8 @@ module.exports = class bw extends Exchange {
// "469849357.2364" // quote volume
// ]
//
let symbol = undefined;
const marketId = this.safeString (ticker, 0);
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
if (market !== undefined) {
symbol = market['symbol'];
} else {
symbol = marketId;
}
const symbol = this.safeSymbol (marketId, market);
const timestamp = this.milliseconds ();
const close = parseFloat (this.safeValue (ticker, 1));
const bid = this.safeValue (ticker, 'bid', {});
@@ -429,7 +422,7 @@ module.exports = class bw extends Exchange {
result[symbol] = ticker;
}
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -557,7 +550,25 @@ module.exports = class bw extends Exchange {
return this.parseTrades (trades, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// "K",
// "305",
// "eth_btc",
// "1591511280",
// "0.02504",
// "0.02504",
// "0.02504",
// "0.02504",
// "0.0123",
// "0",
// "285740.17",
// "1M",
// "false",
// "0.000308"
// ]
//
return [
this.safeTimestamp (ohlcv, 3),
this.safeFloat (ohlcv, 4),
@@ -580,9 +591,18 @@ module.exports = class bw extends Exchange {
request['dataSize'] = limit;
}
const response = await this.publicGetApiDataV1Klines (this.extend (request, params));
//
// {
// "datas":[
// ["K","305","eth_btc","1591511280","0.02504","0.02504","0.02504","0.02504","0.0123","0","285740.17","1M","false","0.000308"],
// ["K","305","eth_btc","1591511220","0.02504","0.02504","0.02504","0.02504","0.0006","0","285740.17","1M","false","0.00001502"],
// ["K","305","eth_btc","1591511100","0.02505","0.02505","0.02504","0.02504","0.0012","-0.0399","285740.17","1M","false","0.00003005"],
// ],
// "resMsg":{"code":"1","method":null,"message":"success !"}
// }
//
const data = this.safeValue (response, 'datas', []);
const ohlcvs = this.parseOHLCVs (data, market, timeframe, since, limit);
return this.sortBy (ohlcvs, 0);
return this.parseOHLCVs (data, market, timeframe, since, limit);
}
async fetchBalance (params = {}) {
@@ -705,9 +725,7 @@ module.exports = class bw extends Exchange {
// }
//
const marketId = this.safeString (order, 'marketId');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
const symbol = this.safeSymbol (marketId, market);
const timestamp = this.safeInteger (order, 'createTime');
let side = this.safeString (order, 'type');
if (side === '0') {
@@ -740,10 +758,12 @@ module.exports = class bw extends Exchange {
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'symbol': this.safeString (market, 'symbol'),
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': cost,
'average': undefined,
@@ -4,7 +4,7 @@
const Exchange = require ('./base/Exchange');
const { TICK_SIZE } = require ('./base/functions/number');
const { AuthenticationError, ExchangeError, ArgumentsRequired, PermissionDenied, InvalidOrder, OrderNotFound, InsufficientFunds, BadRequest, RateLimitExceeded, InvalidNonce, NotSupported } = require ('./base/errors');
const { AuthenticationError, ExchangeError, ArgumentsRequired, PermissionDenied, InvalidOrder, OrderNotFound, InsufficientFunds, BadRequest, RateLimitExceeded, InvalidNonce } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -18,27 +18,29 @@ module.exports = class bybit extends Exchange {
'userAgent': undefined,
'rateLimit': 100,
'has': {
'cancelOrder': true,
'CORS': true,
'fetchMarkets': true,
'fetchBalance': true,
'fetchOHLCV': true,
'cancelAllOrders': true,
'createOrder': true,
'editOrder': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOpenOrders': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchDeposits': true,
'fetchLedger': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchOrderTrades': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchOrderTrades': true,
'createOrder': true,
'cancelOrder': true,
'cancelAllOrders': true,
'fetchTime': true,
'fetchWithdrawals': true,
'fetchDeposits': false,
'fetchTrades': true,
'fetchTransactions': false,
'fetchLedger': true,
'fetchWithdrawals': true,
},
'timeframes': {
'1m': '1',
@@ -60,7 +62,7 @@ module.exports = class bybit extends Exchange {
'test': 'https://api-testnet.bybit.com',
'logo': 'https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg',
'api': 'https://api.bybit.com',
'www': 'https://www.bybit.com/',
'www': 'https://www.bybit.com',
'doc': [
'https://bybit-exchange.github.io/docs/inverse/',
'https://bybit-exchange.github.io/docs/linear/',
@@ -77,31 +79,44 @@ module.exports = class bybit extends Exchange {
'tickers',
'trading-records',
'symbols',
'liq-records',
'mark-price-kline',
'open-interest',
'big-deal',
'account-ratio',
'time',
'announcement',
],
},
'private': {
'get': [
'order/list',
'order',
'stop-order/list',
'stop-order',
'position/list',
'wallet/balance',
'execution/list',
'trade/closed-pnl/list',
'account/lcp',
'exchange-order/list',
],
'post': [
'order/create',
'order/cancel',
'order/cancelAll',
'order/replace',
'stop-order/create',
'stop-order/cancel',
'stop-order/cancelAll',
'stop-order/replace',
],
},
'openapi': {
'get': [
'order/list',
'stop-order/list',
'order/list', // deprecated
'stop-order/list', // deprecated
'wallet/risk-limit/list',
'wallet/risk-limit',
'funding/prev-funding-rate',
'funding/prev-funding',
'funding/predicted-funding',
@@ -110,11 +125,12 @@ module.exports = class bybit extends Exchange {
'wallet/withdraw/list',
],
'post': [
'order/replace',
'stop-order/create',
'stop-order/cancel',
'stop-order/replace',
'order/replace', // deprecated
'stop-order/create', // deprecated
'stop-order/cancel', // deprecated
'stop-order/replace', // deprecated
'position/trading-stop',
'wallet/risk-limit',
],
},
'publicLinear': {
@@ -123,6 +139,7 @@ module.exports = class bybit extends Exchange {
'recent-trading-records',
'funding/prev-funding-rate',
'mark-price-kline',
'risk-limit',
],
},
'privateLinear': {
@@ -132,24 +149,26 @@ module.exports = class bybit extends Exchange {
'stop-order/list',
'stop-order/search',
'position/list',
'position/set-auto-add-margin',
'position/set-leverage',
'position/switch-isolated',
'position/trading-stop',
'position/add-margin',
'trade/execution/list',
'trade/closed-pnl/list',
'risk-limit',
'funding/prev-funding',
'funding/predicted-funding',
],
'post': [
'order/create',
'order/cancel',
'order/cancelAll',
'order/cancel-all',
'order/replace',
'stop-order/create',
'stop-order/cancel',
'stop-order/cancelAll',
'stop-order/cancel-all',
'stop-order/replace',
'position/switch-isolated',
'position/set-auto-add-margin',
'tpsl/switch-mode',
'position/set-leverage',
'position/trading-stop',
'position/add-margin',
],
},
'position': {
@@ -159,7 +178,7 @@ module.exports = class bybit extends Exchange {
},
'user': {
'get': [
'leverage',
'leverage', // deprecated
],
'post': [
'leverage/save',
@@ -259,17 +278,19 @@ module.exports = class bybit extends Exchange {
},
'broad': {
'unknown orderInfo': OrderNotFound, // {"ret_code":-1,"ret_msg":"unknown orderInfo","ext_code":"","ext_info":"","result":null,"time_now":"1584030414.005545","rate_limit_status":99,"rate_limit_reset_ms":1584030414003,"rate_limit":100}
'invalid api_key': AuthenticationError, // {"ret_code":10003,"ret_msg":"invalid api_key","ext_code":"","ext_info":"","result":null,"time_now":"1599547085.415797"}
},
},
'precisionMode': TICK_SIZE,
'options': {
'marketTypes': {
'BTC/USDT': 'linear',
'ETH/USDT': 'linear',
'LTC/USDT': 'linear',
'XTZ/USDT': 'linear',
'LINK/USDT': 'linear',
},
'code': 'BTC',
'fetchBalance': {
'code': 'BTC',
},
'cancelAllOrders': {
'method': 'privatePostOrderCancelAll', // privatePostStopOrderCancelAll
},
@@ -298,8 +319,8 @@ module.exports = class bybit extends Exchange {
return this.milliseconds () - this.options['timeDifference'];
}
async loadTimeDifference () {
const serverTime = await this.fetchTime ();
async loadTimeDifference (params = {}) {
const serverTime = await this.fetchTime (params);
const after = this.milliseconds ();
this.options['timeDifference'] = after - serverTime;
return this.options['timeDifference'];
@@ -347,32 +368,66 @@ module.exports = class bybit extends Exchange {
// time_now: '1583930495.454196'
// }
//
// sandbox/testnet
//
// {
// "ret_code":0,
// "ret_msg":"OK",
// "ext_code":"",
// "ext_info":"",
// "result":[
// {
// "symbol":"BTCUSD",
// "symbol_alias":"BTCUSD",
// "status":"Trading",
// "base_currency":"BTC",
// "quote_currency":"USD",
// "price_scale":2,
// "taker_fee":"0.00075",
// "maker_fee":"-0.00025",
// "leverage_filter":{"min_leverage":1,"max_leverage":100,"leverage_step":"0.01"},
// "price_filter":{"min_price":"0.5","max_price":"999999.5","tick_size":"0.5"},
// "lot_size_filter":{"max_trading_qty":1000000,"min_trading_qty":1,"qty_step":1}
// }
// ],
// "time_now":"1605916574.118500"
// }
//
const markets = this.safeValue (response, 'result', []);
const options = this.safeValue (this.options, 'fetchMarkets', {});
const linearQuoteCurrencies = this.safeValue (options, 'linear', { 'USDT': true });
const result = [];
for (let i = 0; i < markets.length; i++) {
const market = markets[i];
const id = this.safeString (market, 'name');
const id = this.safeString2 (market, 'name', 'symbol');
const baseId = this.safeString (market, 'base_currency');
const quoteId = this.safeString (market, 'quote_currency');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const linear = (quote in linearQuoteCurrencies);
const inverse = !linear;
const symbol = base + '/' + quote;
let symbol = base + '/' + quote;
const baseQuote = base + quote;
if (baseQuote !== id) {
symbol = id;
}
const lotSizeFilter = this.safeValue (market, 'lot_size_filter', {});
const priceFilter = this.safeValue (market, 'price_filter', {});
const precision = {
'amount': this.safeFloat (lotSizeFilter, 'qty_step'),
'price': this.safeFloat (priceFilter, 'tick_size'),
};
const status = this.safeString (market, 'status');
let active = undefined;
if (status !== undefined) {
active = (status === 'Trading');
}
result.push ({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'active': undefined,
'active': active,
'precision': precision,
'taker': this.safeFloat (market, 'taker_fee'),
'maker': this.safeFloat (market, 'maker_fee'),
@@ -404,13 +459,15 @@ module.exports = class bybit extends Exchange {
async fetchBalance (params = {}) {
await this.loadMarkets ();
const defaultCode = this.safeValue (this.options, 'code', 'BTC');
const options = this.safeValue (this.options, 'fetchBalance', {});
const code = this.safeValue (options, 'code', defaultCode);
const currency = this.currency (code);
const request = {
'coin': currency['id'],
};
const request = {};
const coin = this.safeString (params, 'coin');
const code = this.safeString (params, 'code');
if (coin !== undefined) {
request['coin'] = coin;
} else if (code !== undefined) {
const currency = this.currency (code);
request['coin'] = currency['id'];
}
const response = await this.privateGetWalletBalance (this.extend (request, params));
//
// {
@@ -491,13 +548,7 @@ module.exports = class bybit extends Exchange {
//
const timestamp = undefined;
const marketId = this.safeString (ticker, 'symbol');
let symbol = marketId;
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market);
const last = this.safeFloat (ticker, 'last_price');
const open = this.safeFloat (ticker, 'prev_price_24h');
let percentage = this.safeFloat (ticker, 'price_24h_pcnt');
@@ -512,10 +563,7 @@ module.exports = class bybit extends Exchange {
}
const baseVolume = this.safeFloat (ticker, 'turnover_24h');
const quoteVolume = this.safeFloat (ticker, 'volume_24h');
let vwap = undefined;
if (quoteVolume !== undefined && baseVolume !== undefined) {
vwap = quoteVolume / baseVolume;
}
const vwap = this.vwap (baseVolume, quoteVolume);
return {
'symbol': symbol,
'timestamp': timestamp,
@@ -641,7 +689,9 @@ module.exports = class bybit extends Exchange {
return this.filterByArray (tickers, 'symbol', symbols);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// inverse perpetual BTC/USD
//
// {
// symbol: 'BTCUSD',
@@ -653,7 +703,9 @@ module.exports = class bybit extends Exchange {
// close: '7763.5',
// volume: '1259766',
// turnover: '162.32773718999994'
// },
// }
//
// linear perpetual BTC/USDT
//
// {
// "id":143536,
@@ -796,30 +848,12 @@ module.exports = class bybit extends Exchange {
// }
//
const id = this.safeString2 (trade, 'id', 'exec_id');
let symbol = undefined;
let base = undefined;
const marketId = this.safeString (trade, 'symbol');
let amount = this.safeFloat2 (trade, 'qty', 'exec_qty');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
const amount = this.safeFloat2 (trade, 'qty', 'exec_qty');
let cost = this.safeFloat (trade, 'exec_value');
const price = this.safeFloat2 (trade, 'price', 'exec_price');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
base = market['base'];
}
if (market !== undefined) {
if (symbol === undefined) {
symbol = market['symbol'];
base = market['base'];
}
// if private trade
if ('exec_fee' in trade) {
if (market['inverse']) {
amount = this.safeFloat (trade, 'exec_value');
cost = this.safeFloat (trade, 'exec_qty');
}
}
}
const price = this.safeFloat2 (trade, 'exec_price', 'price');
if (cost === undefined) {
if (amount !== undefined) {
if (price !== undefined) {
@@ -837,9 +871,10 @@ module.exports = class bybit extends Exchange {
const feeCost = this.safeFloat (trade, 'exec_fee');
let fee = undefined;
if (feeCost !== undefined) {
const feeCurrencyCode = market['inverse'] ? market['base'] : market['quote'];
fee = {
'cost': feeCost,
'currency': base,
'currency': feeCurrencyCode,
'rate': this.safeFloat (trade, 'fee_rate'),
};
}
@@ -956,11 +991,11 @@ module.exports = class bybit extends Exchange {
'Rejected': 'rejected', // order is triggered but failed upon being placed
'New': 'open',
'PartiallyFilled': 'open',
'Filled': 'filled',
'Filled': 'closed',
'Cancelled': 'canceled',
'PendingCancel': 'canceling', // the engine has received the cancellation but there is no guarantee that it will be successful
// conditional orders
'Active': 'closed', // order is triggered and placed successfully
'Active': 'open', // order is triggered and placed successfully
'Untriggered': 'open', // order waits to be triggered
'Triggered': 'closed', // order is triggered
// 'Cancelled': 'canceled', // order is cancelled
@@ -970,6 +1005,16 @@ module.exports = class bybit extends Exchange {
return this.safeString (statuses, status, status);
}
parseTimeInForce (timeInForce) {
const timeInForces = {
'GoodTillCancel': 'GTC',
'ImmediateOrCancel': 'IOC',
'FillOrKill': 'FOK',
'PostOnly': 'PO',
};
return this.safeString (timeInForces, timeInForce, timeInForce);
}
parseOrder (order, market = undefined) {
//
// createOrder
@@ -1026,34 +1071,60 @@ module.exports = class bybit extends Exchange {
// "order_id" : "dd2504b9-0157-406a-99e1-efa522373944"
// }
//
// conditional order
//
// {
// "user_id":##,
// "symbol":"BTCUSD",
// "side":"Buy",
// "order_type":"Market",
// "price":0,
// "qty":10,
// "time_in_force":"GoodTillCancel",
// "stop_order_type":"Stop",
// "trigger_by":"LastPrice",
// "base_price":11833,
// "order_status":"Untriggered",
// "ext_fields":{
// "stop_order_type":"Stop",
// "trigger_by":"LastPrice",
// "base_price":11833,
// "expected_direction":"Rising",
// "trigger_price":12400,
// "close_on_trigger":true,
// "op_from":"api",
// "remark":"145.53.159.48",
// "o_req_num":0
// },
// "leaves_qty":10,
// "leaves_value":0.00080645,
// "reject_reason":null,
// "cross_seq":-1,
// "created_at":"2020-08-21T09:18:48.000Z",
// "updated_at":"2020-08-21T09:18:48.000Z",
// "stop_px":12400,
// "stop_order_id":"3f3b54b1-3379-42c7-8510-44f4d9915be0"
// }
//
const marketId = this.safeString (order, 'symbol');
market = this.safeMarket (marketId, market);
let symbol = undefined;
let base = undefined;
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
const id = this.safeString (order, 'order_id');
const price = this.safeFloat (order, 'price');
const average = this.safeFloat (order, 'average_price');
let amount = undefined;
let cost = undefined;
let filled = undefined;
let remaining = undefined;
const id = this.safeString2 (order, 'order_id', 'stop_order_id');
const type = this.safeStringLower (order, 'order_type');
let price = this.safeFloat (order, 'price');
if (price === 0.0) {
price = undefined;
}
let average = this.safeFloat (order, 'average_price');
const amount = this.safeFloat (order, 'qty');
let cost = this.safeFloat (order, 'cum_exec_value');
let filled = this.safeFloat (order, 'cum_exec_qty');
let remaining = this.safeFloat (order, 'leaves_qty');
if (market !== undefined) {
symbol = market['symbol'];
base = market['base'];
if (market['inverse']) {
cost = this.safeFloat (order, 'cum_exec_qty');
filled = this.safeFloat (order, 'cum_exec_value');
remaining = this.safeFloat (order, 'leaves_value');
amount = this.sum (filled, remaining);
} else {
amount = this.safeFloat (order, 'qty');
cost = this.safeFloat (order, 'cum_exec_value');
filled = this.safeFloat (order, 'cum_exec_qty');
remaining = this.safeFloat (order, 'leaves_qty');
}
}
let lastTradeTimestamp = this.safeTimestamp (order, 'last_exec_time');
if (lastTradeTimestamp === 0) {
@@ -1071,8 +1142,14 @@ module.exports = class bybit extends Exchange {
cost = price * filled;
}
}
if ((type === 'market') && (cost !== undefined) && (cost > 0)) {
price = undefined;
if (average === undefined) {
average = filled / cost;
}
}
}
const status = this.parseOrderStatus (this.safeString (order, 'order_status'));
const status = this.parseOrderStatus (this.safeString2 (order, 'order_status', 'stop_order_status'));
const side = this.safeStringLower (order, 'side');
let feeCost = this.safeFloat (order, 'cum_exec_fee');
let fee = undefined;
@@ -1083,11 +1160,12 @@ module.exports = class bybit extends Exchange {
'currency': base,
};
}
const type = this.safeStringLower (order, 'order_type');
let clientOrderId = this.safeString (order, 'order_link_id');
if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) {
clientOrderId = undefined;
}
const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force'));
const stopPrice = this.safeFloat (order, 'stop_px');
return {
'info': order,
'id': id,
@@ -1097,8 +1175,10 @@ module.exports = class bybit extends Exchange {
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'side': side,
'price': price,
'stopPrice': stopPrice,
'amount': amount,
'cost': cost,
'average': average,
@@ -1209,20 +1289,26 @@ module.exports = class bybit extends Exchange {
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
let qty = this.amountToPrecision (symbol, amount);
if (market['inverse']) {
qty = parseInt (qty);
} else {
qty = parseFloat (qty);
}
const request = {
// orders ---------------------------------------------------------
'side': this.capitalize (side),
'symbol': market['id'],
'order_type': this.capitalize (type),
'qty': this.amountToPrecision (symbol, amount), // order quantity in USD, integer only
// 'price': this.priceToPrecision (symbol, price), // required for limit orders
'qty': qty, // order quantity in USD, integer only
// 'price': parseFloat (this.priceToPrecision (symbol, price)), // required for limit orders
'time_in_force': 'GoodTillCancel', // ImmediateOrCancel, FillOrKill, PostOnly
// 'take_profit': 123.45, // take profit price, only take effect upon opening the position
// 'stop_loss': 123.45, // stop loss price, only take effect upon opening the position
// 'reduce_only': false, // reduce only
// 'reduce_only': false, // reduce only, required for linear orders
// when creating a closing order, bybit recommends a True value for
// close_on_trigger to avoid failing due to insufficient available margin
// 'close_on_trigger': false,
// 'close_on_trigger': false, required for linear orders
// 'order_link_id': 'string', // unique client order id, max 36 characters
// conditional orders ---------------------------------------------
// base_price is used to compare with the value of stop_px, to decide
@@ -1239,24 +1325,34 @@ module.exports = class bybit extends Exchange {
}
if (priceIsRequired) {
if (price !== undefined) {
request['price'] = this.priceToPrecision (symbol, price);
request['price'] = parseFloat (this.priceToPrecision (symbol, price));
} else {
throw new ArgumentsRequired (this.id + ' createOrder requires a price argument for a ' + type + ' order');
}
}
const stopPx = this.safeValue (params, 'stop_px');
const clientOrderId = this.safeString2 (params, 'order_link_id', 'clientOrderId');
if (clientOrderId !== undefined) {
request['order_link_id'] = clientOrderId;
params = this.omit (params, [ 'order_link_id', 'clientOrderId' ]);
}
const stopPx = this.safeValue2 (params, 'stop_px', 'stopPrice');
const basePrice = this.safeValue (params, 'base_price');
const marketTypes = this.safeValue (this.options, 'marketTypes', {});
const marketType = this.safeString (marketTypes, symbol);
let method = (marketType === 'linear') ? 'privateLinearPostOrderCreate' : 'privatePostOrderCreate';
if (marketType === 'linear') {
method = 'privateLinearPostOrderCreate';
request['reduce_only'] = false;
request['close_on_trigger'] = false;
}
if (stopPx !== undefined) {
if (basePrice === undefined) {
throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order');
} else {
method = (marketType === 'linear') ? 'privateLinearPostStopOrderCreate' : 'openapiPostStopOrderCreate';
request['stop_px'] = this.priceToPrecision (symbol, stopPx);
request['base_price'] = this.priceToPrecision (symbol, basePrice);
params = this.omit (params, [ 'stop_px', 'base_price' ]);
request['stop_px'] = parseFloat (this.priceToPrecision (symbol, stopPx));
request['base_price'] = parseFloat (this.priceToPrecision (symbol, basePrice));
params = this.omit (params, [ 'stop_px', 'stopPrice', 'base_price' ]);
}
} else if (basePrice !== undefined) {
throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order');
@@ -1349,9 +1445,6 @@ module.exports = class bybit extends Exchange {
}
const marketTypes = this.safeValue (this.options, 'marketTypes', {});
const marketType = this.safeString (marketTypes, symbol);
if (marketType === 'linear') {
throw new NotSupported (this.id + ' does not support editOrder for ' + marketType + ' ' + symbol + ' market type');
}
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
@@ -1364,20 +1457,20 @@ module.exports = class bybit extends Exchange {
// 'stop_order_id': id, // only for conditional orders
// 'p_r_trigger_price': 123.45, // new trigger price also known as stop_px
};
let method = (marketType === 'linear') ? 'privateLinearPostOrderReplace' : 'openapiPostOrderReplace';
const stopOrderId = this.safeString (params, 'stop_order_id');
let method = 'openapiPostOrderReplace';
if (stopOrderId !== undefined) {
method = 'openapiPostStopOrderReplace';
method = (marketType === 'linear') ? 'privateLinearPostStopOrderReplace' : 'openapiPostStopOrderReplace';
request['stop_order_id'] = stopOrderId;
params = this.omit (params, [ 'stop_order_id' ]);
} else {
request['order_id'] = id;
}
if (amount !== undefined) {
request['p_r_qty'] = this.amountToPrecision (symbol, amount);
request['p_r_qty'] = parseInt (this.amountToPrecision (symbol, amount));
}
if (price !== undefined) {
request['p_r_price'] = this.priceToPrecision (symbol, price);
request['p_r_price'] = parseFloat (this.priceToPrecision (symbol, price));
}
const response = await this[method] (this.extend (request, params));
//
@@ -1417,7 +1510,7 @@ module.exports = class bybit extends Exchange {
async cancelOrder (id, symbol = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument');
throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument');
}
await this.loadMarkets ();
const market = this.market (symbol);
@@ -1687,6 +1780,8 @@ module.exports = class bybit extends Exchange {
const method = (marketType === 'linear') ? 'privateLinearGetTradeExecutionList' : 'privateGetExecutionList';
const response = await this[method] (this.extend (request, params));
//
// inverse
//
// {
// "ret_code": 0,
// "ret_msg": "OK",
@@ -1726,8 +1821,48 @@ module.exports = class bybit extends Exchange {
// "rate_limit": 120
// }
//
// linear
//
// {
// "ret_code":0,
// "ret_msg":"OK",
// "ext_code":"",
// "ext_info":"",
// "result":{
// "current_page":1,
// "data":[
// {
// "order_id":"b59418ec-14d4-4ef9-b9f4-721d5d576974",
// "order_link_id":"",
// "side":"Sell",
// "symbol":"BTCUSDT",
// "exec_id":"0327284d-faec-5191-bd89-acc5b4fafda9",
// "price":0.5,
// "order_price":0.5,
// "order_qty":0.01,
// "order_type":"Market",
// "fee_rate":0.00075,
// "exec_price":9709.5,
// "exec_type":"Trade",
// "exec_qty":0.01,
// "exec_fee":0.07282125,
// "exec_value":97.095,
// "leaves_qty":0,
// "closed_size":0.01,
// "last_liquidity_ind":"RemovedLiquidity",
// "trade_time":1591648052,
// "trade_time_ms":1591648052861
// }
// ]
// },
// "time_now":"1591736501.979264",
// "rate_limit_status":119,
// "rate_limit_reset_ms":1591736501974,
// "rate_limit":120
// }
//
const result = this.safeValue (response, 'result', {});
const trades = this.safeValue (result, 'trade_list', []);
const trades = this.safeValue2 (result, 'trade_list', 'data', []);
return this.parseTrades (trades, market, since, limit);
}
@@ -2044,7 +2179,13 @@ module.exports = class bybit extends Exchange {
'recv_window': this.options['recvWindow'],
'timestamp': timestamp,
});
const auth = this.rawencode (this.keysort (query));
let auth = this.rawencode (this.keysort (query));
// https://github.com/ccxt/ccxt/issues/7377
// https://github.com/ccxt/ccxt/issues/7515
// bybit encodes whole floats as integers without .0 for conditional stop-orders only
if (path.indexOf ('stop-order') >= 0) {
auth = auth.replace ('.0&', '&');
}
const signature = this.hmac (this.encode (auth), this.encode (this.secret));
if (method === 'POST') {
body = this.json (this.extend (query, {
@@ -19,20 +19,27 @@ module.exports = class bytetrade extends Exchange {
'certified': true,
// new metainfo interface
'has': {
'cancelOrder': true,
'CORS': false,
'createOrder': true,
'fetchBalance': true,
'fetchBidsAsks': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'CORS': false,
'fetchBidsAsks': true,
'fetchTickers': true,
'fetchOHLCV': true,
'fetchMyTrades': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'withdraw': true,
'fetchDeposits': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchWithdrawals': true,
'withdraw': true,
},
'timeframes': {
'1m': '1m',
@@ -53,11 +60,11 @@ module.exports = class bytetrade extends Exchange {
},
'logo': 'https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg',
'api': {
'market': 'https://api-v2.bytetrade.com',
'public': 'https://api-v2.bytetrade.com',
'market': 'https://api-v2.byte-trade.com',
'public': 'https://api-v2.byte-trade.com',
},
'www': 'https://www.byte-trade.com',
'doc': 'https://github.com/Bytetrade/bytetrade-official-api-docs/wiki',
'doc': 'https://docs.byte-trade.com/#description',
},
'api': {
'market': {
@@ -98,8 +105,10 @@ module.exports = class bytetrade extends Exchange {
},
},
'commonCurrencies': {
'1': 'ByteTrade',
'44': 'ByteHub',
'48': 'Blocktonic',
'133': 'TerraCredit',
},
'exceptions': {
'vertify error': AuthenticationError, // typo on the exchange side, 'vertify'
@@ -191,10 +200,7 @@ module.exports = class bytetrade extends Exchange {
'code': code,
'name': name,
'active': active,
'precision': {
'amount': amountPrecision,
'price': undefined,
},
'precision': amountPrecision,
'fee': undefined,
'limits': {
'amount': { 'min': undefined, 'max': undefined },
@@ -223,11 +229,14 @@ module.exports = class bytetrade extends Exchange {
const id = this.safeString (market, 'symbol');
let base = this.safeString (market, 'baseName');
let quote = this.safeString (market, 'quoteName');
const normalBase = base.split ('@')[0];
const normalQuote = quote.split ('@')[0];
const normalSymbol = normalBase + '/' + normalQuote;
const baseId = this.safeString (market, 'base');
const quoteId = this.safeString (market, 'quote');
const normalBase = base.split ('@' + baseId)[0];
let normalQuote = quote.split ('@' + quoteId)[0];
if (quoteId === '126') {
normalQuote = 'ZAR'; // The id 126 coin is a special coin whose name on the chain is actually ZAR, but it is changed to ZCN after creation, so it must be changed to ZAR when placing the transaction in the chain
}
const normalSymbol = normalBase + '/' + normalQuote;
if (baseId in this.commonCurrencies) {
base = this.commonCurrencies[baseId];
}
@@ -432,14 +441,24 @@ module.exports = class bytetrade extends Exchange {
return this.parseTickers (rawTickers, symbols);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// 1591505760000,
// "242.7",
// "242.76",
// "242.69",
// "242.76",
// "0.1892"
// ]
//
return [
ohlcv[0],
parseFloat (ohlcv[1]),
parseFloat (ohlcv[2]),
parseFloat (ohlcv[3]),
parseFloat (ohlcv[4]),
parseFloat (ohlcv[5]),
this.safeInteger (ohlcv, 0),
this.safeFloat (ohlcv, 1),
this.safeFloat (ohlcv, 2),
this.safeFloat (ohlcv, 3),
this.safeFloat (ohlcv, 4),
this.safeFloat (ohlcv, 5),
];
}
@@ -457,6 +476,13 @@ module.exports = class bytetrade extends Exchange {
request['limit'] = limit;
}
const response = await this.marketGetKlines (this.extend (request, params));
//
// [
// [1591505760000,"242.7","242.76","242.69","242.76","0.1892"],
// [1591505820000,"242.77","242.83","242.7","242.72","0.6378"],
// [1591505880000,"242.72","242.73","242.61","242.72","0.4141"],
// ]
//
return this.parseOHLCVs (response, market, timeframe, since, limit);
}
@@ -469,7 +495,7 @@ module.exports = class bytetrade extends Exchange {
const type = this.safeString (trade, 'type');
const takerOrMaker = this.safeString (trade, 'takerOrMaker');
const side = this.safeString (trade, 'side');
const datetime = this.safeString (trade, 'datetime');
const datetime = this.iso8601 (timestamp); // this.safeString (trade, 'datetime');
const order = this.safeString (trade, 'order');
const fee = this.safeValue (trade, 'fee');
let symbol = undefined;
@@ -553,8 +579,10 @@ module.exports = class bytetrade extends Exchange {
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': cost,
'average': average,
@@ -590,11 +618,11 @@ module.exports = class bytetrade extends Exchange {
const baseId = market['baseId'];
const baseCurrency = this.currency (market['base']);
const amountTruncated = this.amountToPrecision (symbol, amount);
const amountChain = this.toWei (amountTruncated, baseCurrency['precision']['amount']);
const amountChain = this.toWei (amountTruncated, baseCurrency['precision']);
const quoteId = market['quoteId'];
const quoteCurrency = this.currency (market['quote']);
const priceRounded = this.priceToPrecision (symbol, price);
const priceChain = this.toWei (priceRounded, quoteCurrency['precision']['amount']);
const priceChain = this.toWei (priceRounded, quoteCurrency['precision']);
const now = this.milliseconds ();
const expiration = this.milliseconds ();
let datetime = this.iso8601 (now);
@@ -604,6 +632,8 @@ module.exports = class bytetrade extends Exchange {
const defaultDappId = 'Sagittarius';
const dappId = this.safeString (params, 'dappId', defaultDappId);
const defaultFee = this.safeString (this.options, 'fee', '300000000000000');
const totalFeeRate = this.safeString (params, 'totalFeeRate', 8);
const chainFeeRate = this.safeString (params, 'chainFeeRate', 1);
const fee = this.safeString (params, 'fee', defaultFee);
const eightBytes = this.integerPow ('2', '64');
const allByteStringArray = [
@@ -628,7 +658,10 @@ module.exports = class bytetrade extends Exchange {
this.numberToLE (0, 2),
this.numberToLE (Math.floor (now / 1000), 4),
this.numberToLE (Math.floor (expiration / 1000), 4),
this.numberToLE (0, 2),
this.numberToLE (1, 1),
this.numberToLE (parseInt (chainFeeRate), 2),
this.numberToLE (1, 1),
this.numberToLE (parseInt (totalFeeRate), 2),
this.numberToLE (parseInt (quoteId), 4),
this.numberToLE (parseInt (baseId), 4),
this.numberToLE (0, 1),
@@ -658,7 +691,10 @@ module.exports = class bytetrade extends Exchange {
this.numberToLE (0, 2),
this.numberToLE (Math.floor (now / 1000), 4),
this.numberToLE (Math.floor (expiration / 1000), 4),
this.numberToLE (0, 2),
this.numberToLE (1, 1),
this.numberToLE (parseInt (chainFeeRate), 2),
this.numberToLE (1, 1),
this.numberToLE (parseInt (totalFeeRate), 2),
this.numberToLE (parseInt (quoteId), 4),
this.numberToLE (parseInt (baseId), 4),
this.numberToLE (0, 1),
@@ -681,7 +717,7 @@ module.exports = class bytetrade extends Exchange {
const bytestring = this.binaryConcatArray (allByteStringArray);
const hash = this.hash (bytestring, 'sha256', 'hex');
const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true);
const recoveryParam = this.decode (this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)));
const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1));
const mySignature = recoveryParam + signature['r'] + signature['s'];
const operation = {
'now': datetime,
@@ -696,6 +732,8 @@ module.exports = class bytetrade extends Exchange {
'use_btt_as_fee': false,
'money_id': parseInt (quoteId),
'stock_id': parseInt (baseId),
'custom_no_btt_fee_rate': parseInt (totalFeeRate),
'custom_btt_fee_rate': parseInt (chainFeeRate),
};
const fatty = {
'timestamp': datetime,
@@ -865,7 +903,7 @@ module.exports = class bytetrade extends Exchange {
const bytestring = this.binaryConcatArray (byteStringArray);
const hash = this.hash (bytestring, 'sha256', 'hex');
const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true);
const recoveryParam = this.decode (this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)));
const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1));
const mySignature = recoveryParam + signature['r'] + signature['s'];
const operation = {
'fee': feeAmount,
@@ -926,8 +964,8 @@ module.exports = class bytetrade extends Exchange {
}
await this.loadMarkets ();
const currency = this.currency (code);
const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING);
const amountChain = this.toWei (amountTruncate, currency['precision']['amount']);
const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING);
const amountChain = this.toWei (amountTruncate, currency['precision']);
const assetType = parseInt (currency['id']);
const now = this.milliseconds ();
const expiration = now;
@@ -967,7 +1005,7 @@ module.exports = class bytetrade extends Exchange {
const bytestring = this.binaryConcatArray (byteStringArray);
const hash = this.hash (bytestring, 'sha256', 'hex');
const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true);
const recoveryParam = this.decode (this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)));
const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1));
const mySignature = recoveryParam + signature['r'] + signature['s'];
const operation = {
'fee': '300000000000000',
@@ -1207,7 +1245,7 @@ module.exports = class bytetrade extends Exchange {
const feeAmount = '300000000000000';
const currency = this.currency (code);
const coinId = currency['id'];
const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING);
const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING);
const amountChain = this.toWei (amountTruncate, currency['info']['externalPrecision']);
const eightBytes = this.integerPow ('2', '64');
let assetFee = 0;
@@ -1273,7 +1311,7 @@ module.exports = class bytetrade extends Exchange {
const bytestring = this.binaryConcatArray (byteStringArray);
const hash = this.hash (bytestring, 'sha256', 'hex');
const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true);
const recoveryParam = this.decode (this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)));
const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1));
const mySignature = recoveryParam + signature['r'] + signature['s'];
let fatty = undefined;
let request = undefined;
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, AuthenticationError, NullResponse, InvalidOrder, NotSupported, InsufficientFunds, InvalidNonce, OrderNotFound, RateLimitExceeded } = require ('./base/errors');
const { ExchangeError, ArgumentsRequired, AuthenticationError, NullResponse, InvalidOrder, NotSupported, InsufficientFunds, InvalidNonce, OrderNotFound, RateLimitExceeded, DDoSProtection } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -15,15 +15,23 @@ module.exports = class cex extends Exchange {
'countries': [ 'GB', 'EU', 'CY', 'RU' ],
'rateLimit': 1500,
'has': {
'cancelOrder': true,
'CORS': false,
'fetchCurrencies': true,
'fetchTickers': true,
'fetchOHLCV': true,
'fetchOrder': true,
'fetchOpenOrders': true,
'createOrder': true,
'editOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchMarkets': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
},
'timeframes': {
'1m': '1m',
@@ -125,9 +133,10 @@ module.exports = class cex extends Exchange {
'Nonce must be incremented': InvalidNonce,
'Invalid Order': InvalidOrder,
'Order not found': OrderNotFound,
'Rate limit exceeded': RateLimitExceeded,
'limit exceeded': RateLimitExceeded, // {"error":"rate limit exceeded"}
'Invalid API key': AuthenticationError,
'There was an error while placing your order': InvalidOrder,
'Sorry, too many clients already': DDoSProtection,
},
},
'options': {
@@ -137,7 +146,7 @@ module.exports = class cex extends Exchange {
'status': {
'c': 'canceled',
'd': 'closed',
'cd': 'closed',
'cd': 'canceled',
'a': 'open',
},
},
@@ -393,14 +402,24 @@ module.exports = class cex extends Exchange {
return this.parseOrderBook (response, timestamp);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// 1591403940,
// 0.024972,
// 0.024972,
// 0.024969,
// 0.024969,
// 0.49999900
// ]
//
return [
ohlcv[0] * 1000,
ohlcv[1],
ohlcv[2],
ohlcv[3],
ohlcv[4],
ohlcv[5],
this.safeTimestamp (ohlcv, 0),
this.safeFloat (ohlcv, 1),
this.safeFloat (ohlcv, 2),
this.safeFloat (ohlcv, 3),
this.safeFloat (ohlcv, 4),
this.safeFloat (ohlcv, 5),
];
}
@@ -423,8 +442,15 @@ module.exports = class cex extends Exchange {
};
try {
const response = await this.publicGetOhlcvHdYyyymmddPair (this.extend (request, params));
//
// {
// "time":20200606,
// "data1m":"[[1591403940,0.024972,0.024972,0.024969,0.024969,0.49999900]]",
// }
//
const key = 'data' + this.timeframes[timeframe];
const ohlcvs = JSON.parse (response[key]);
const data = this.safeString (response, key);
const ohlcvs = JSON.parse (data);
return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
} catch (e) {
if (e instanceof NullResponse) {
@@ -484,7 +510,7 @@ module.exports = class cex extends Exchange {
const market = this.markets[symbol];
result[symbol] = this.parseTicker (ticker, market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -856,8 +882,10 @@ module.exports = class cex extends Exchange {
'status': status,
'symbol': symbol,
'type': (price === undefined) ? 'market' : 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -904,7 +932,108 @@ module.exports = class cex extends Exchange {
'id': id.toString (),
};
const response = await this.privatePostGetOrderTx (this.extend (request, params));
return this.parseOrder (response['data']);
const data = this.safeValue (response, 'data', {});
//
// {
// "id": "5442731603",
// "type": "sell",
// "time": 1516132358071,
// "lastTxTime": 1516132378452,
// "lastTx": "5442734452",
// "pos": null,
// "user": "up106404164",
// "status": "d",
// "symbol1": "ETH",
// "symbol2": "EUR",
// "amount": "0.50000000",
// "kind": "api",
// "price": "923.3386",
// "tfacf": "1",
// "fa:EUR": "0.55",
// "ta:EUR": "369.77",
// "remains": "0.00000000",
// "tfa:EUR": "0.22",
// "tta:EUR": "91.95",
// "a:ETH:cds": "0.50000000",
// "a:EUR:cds": "461.72",
// "f:EUR:cds": "0.77",
// "tradingFeeMaker": "0.15",
// "tradingFeeTaker": "0.23",
// "tradingFeeStrategy": "userVolumeAmount",
// "tradingFeeUserVolumeAmount": "2896912572",
// "orderId": "5442731603",
// "next": false,
// "vtx": [
// {
// "id": "5442734452",
// "type": "sell",
// "time": "2018-01-16T19:52:58.452Z",
// "user": "up106404164",
// "c": "user:up106404164:a:EUR",
// "d": "order:5442731603:a:EUR",
// "a": "104.53000000",
// "amount": "104.53000000",
// "balance": "932.71000000",
// "symbol": "EUR",
// "order": "5442731603",
// "buy": "5442734443",
// "sell": "5442731603",
// "pair": null,
// "pos": null,
// "office": null,
// "cs": "932.71",
// "ds": 0,
// "price": 923.3386,
// "symbol2": "ETH",
// "fee_amount": "0.16"
// },
// {
// "id": "5442731609",
// "type": "sell",
// "time": "2018-01-16T19:52:38.071Z",
// "user": "up106404164",
// "c": "user:up106404164:a:EUR",
// "d": "order:5442731603:a:EUR",
// "a": "91.73000000",
// "amount": "91.73000000",
// "balance": "563.49000000",
// "symbol": "EUR",
// "order": "5442731603",
// "buy": "5442618127",
// "sell": "5442731603",
// "pair": null,
// "pos": null,
// "office": null,
// "cs": "563.49",
// "ds": 0,
// "price": 924.0092,
// "symbol2": "ETH",
// "fee_amount": "0.22"
// },
// {
// "id": "5442731604",
// "type": "sell",
// "time": "2018-01-16T19:52:38.071Z",
// "user": "up106404164",
// "c": "order:5442731603:a:ETH",
// "d": "user:up106404164:a:ETH",
// "a": "0.50000000",
// "amount": "-0.50000000",
// "balance": "15.80995000",
// "symbol": "ETH",
// "order": "5442731603",
// "buy": null,
// "sell": null,
// "pair": null,
// "pos": null,
// "office": null,
// "cs": "0.50000000",
// "ds": "15.80995000"
// }
// ]
// }
//
return this.parseOrder (data);
}
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -141,6 +141,9 @@ module.exports = class coinbase extends Exchange {
'rate_limit_exceeded': RateLimitExceeded, // 429 Rate limit exceeded
'internal_server_error': ExchangeError, // 500 Internal server error
},
'commonCurrencies': {
'CGLD': 'CELO',
},
'options': {
'fetchCurrencies': {
'expires': 5000,
@@ -156,8 +159,16 @@ module.exports = class coinbase extends Exchange {
async fetchTime (params = {}) {
const response = await this.publicGetTime (params);
//
// {
// "data": {
// "epoch": 1589295679,
// "iso": "2020-05-12T15:01:19Z"
// }
// }
//
const data = this.safeValue (response, 'data', {});
return this.parse8601 (this.safeString (data, 'iso'));
return this.safeTimestamp (data, 'epoch');
}
async fetchAccounts (params = {}) {
@@ -459,8 +470,8 @@ module.exports = class coinbase extends Exchange {
const id = this.safeString (trade, 'id');
const timestamp = this.parse8601 (this.safeValue (trade, 'created_at'));
if (market === undefined) {
const baseId = this.safeString (totalObject, 'currency');
const quoteId = this.safeString (amountObject, 'currency');
const baseId = this.safeString (amountObject, 'currency');
const quoteId = this.safeString (totalObject, 'currency');
if ((baseId !== undefined) && (quoteId !== undefined)) {
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
@@ -474,7 +485,7 @@ module.exports = class coinbase extends Exchange {
const amount = this.safeFloat (amountObject, 'amount');
let price = undefined;
if (cost !== undefined) {
if (amount !== undefined) {
if ((amount !== undefined) && (amount > 0)) {
price = cost / amount;
}
}
@@ -722,13 +733,17 @@ module.exports = class coinbase extends Exchange {
async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
let currency = undefined;
if (code !== undefined) {
currency = this.currency (code);
}
const request = await this.prepareAccountRequestWithCurrencyCode (code, limit, params);
const query = this.omit (params, ['account_id', 'accountId']);
// for pagination use parameter 'starting_after'
// the value for the next page can be obtained from the result of the previous call in the 'pagination' field
// eg: instance.last_json_response.pagination.next_starting_after
const response = await this.privateGetAccountsAccountIdTransactions (this.extend (request, query));
return this.parseLedger (response['data'], undefined, since, limit);
return this.parseLedger (response['data'], currency, since, limit);
}
parseLedgerEntryStatus (status) {
@@ -3,7 +3,8 @@
// ----------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { InsufficientFunds, ArgumentsRequired, ExchangeError, InvalidOrder, InvalidAddress, AuthenticationError, NotSupported, OrderNotFound, OnMaintenance, RateLimitExceeded } = require ('./base/errors');
const { InsufficientFunds, ArgumentsRequired, ExchangeError, InvalidOrder, InvalidAddress, AuthenticationError, NotSupported, OrderNotFound, OnMaintenance, PermissionDenied, RateLimitExceeded } = require ('./base/errors');
const { TICK_SIZE } = require ('./base/functions/number');
// ----------------------------------------------------------------------------
@@ -18,21 +19,31 @@ module.exports = class coinbasepro extends Exchange {
'pro': true,
'has': {
'cancelAllOrders': true,
'cancelOrder': true,
'CORS': true,
'createDepositAddress': true,
'createOrder': true,
'deposit': true,
'fetchAccounts': true,
'fetchBalance': true,
'fetchCurrencies': true,
'fetchClosedOrders': true,
'fetchDepositAddress': true,
'createDepositAddress': true,
'fetchDepositAddress': false, // the exchange does not have this method, only createDepositAddress, see https://github.com/ccxt/ccxt/pull/7405
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderTrades': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchOrderTrades': true,
'fetchTime': true,
'fetchTicker': true,
'fetchTrades': true,
'fetchTransactions': true,
'withdraw': true,
'fetchDeposits': true,
'fetchWithdrawals': true,
},
'timeframes': {
'1m': 60,
@@ -69,6 +80,7 @@ module.exports = class coinbasepro extends Exchange {
'get': [
'currencies',
'products',
'products/{id}',
'products/{id}/book',
'products/{id}/candles',
'products/{id}/stats',
@@ -85,17 +97,32 @@ module.exports = class coinbasepro extends Exchange {
'accounts/{id}/ledger',
'accounts/{id}/transfers',
'coinbase-accounts',
'coinbase-accounts/{id}/addresses',
'fills',
'funding',
'fees',
'margin/profile_information',
'margin/buying_power',
'margin/withdrawal_power',
'margin/withdrawal_power_all',
'margin/exit_plan',
'margin/liquidation_history',
'margin/position_refresh_amounts',
'margin/status',
'oracle',
'orders',
'orders/{id}',
'orders/client:{client_oid}',
'otc/orders',
'payment-methods',
'position',
'reports/{id}',
'profiles',
'profiles/{id}',
'reports/{report_id}',
'transfers',
'transfers/{transfer_id}',
'users/self/trailing-volume',
'users/self/exchange-limits',
'withdrawals/fee-estimate',
],
'post': [
'conversions',
@@ -106,17 +133,24 @@ module.exports = class coinbasepro extends Exchange {
'orders',
'position/close',
'profiles/margin-transfer',
'profiles/transfer',
'reports',
'withdrawals/coinbase',
'withdrawals/coinbase-account',
'withdrawals/crypto',
'withdrawals/payment-method',
],
'delete': [
'orders',
'orders/client:{client_oid}',
'orders/{id}',
],
},
},
'commonCurrencies': {
'CGLD': 'CELO',
},
'precisionMode': TICK_SIZE,
'fees': {
'trading': {
'tierBased': true, // complicated tier system per coin
@@ -154,6 +188,8 @@ module.exports = class coinbasepro extends Exchange {
'Invalid Passphrase': AuthenticationError,
'Invalid order id': InvalidOrder,
'Private rate limit exceeded': RateLimitExceeded,
'Trading pair not available': PermissionDenied,
'Product not found': InvalidOrder,
},
'broad': {
'Order already done': OrderNotFound,
@@ -161,13 +197,108 @@ module.exports = class coinbasepro extends Exchange {
'price too small': InvalidOrder,
'price too precise': InvalidOrder,
'under maintenance': OnMaintenance,
'size is too small': InvalidOrder,
'Cancel only mode': OnMaintenance, // https://github.com/ccxt/ccxt/issues/7690
},
},
});
}
async fetchCurrencies (params = {}) {
const response = await this.publicGetCurrencies (params);
//
// [
// {
// id: 'XTZ',
// name: 'Tezos',
// min_size: '0.000001',
// status: 'online',
// message: '',
// max_precision: '0.000001',
// convertible_to: [],
// details: {
// type: 'crypto',
// symbol: 'Τ',
// network_confirmations: 60,
// sort_order: 53,
// crypto_address_link: 'https://tzstats.com/{{address}}',
// crypto_transaction_link: 'https://tzstats.com/{{txId}}',
// push_payment_methods: [ 'crypto' ],
// group_types: [],
// display_name: '',
// processing_time_seconds: 0,
// min_withdrawal_amount: 1
// }
// }
// ]
//
const result = {};
for (let i = 0; i < response.length; i++) {
const currency = response[i];
const id = this.safeString (currency, 'id');
const name = this.safeString (currency, 'name');
const code = this.safeCurrencyCode (id);
const details = this.safeValue (currency, 'details', {});
const precision = this.safeFloat (currency, 'max_precision');
const status = this.safeString (currency, 'status');
const active = (status === 'online');
result[code] = {
'id': id,
'code': code,
'info': currency,
'type': this.safeString (details, 'type'),
'name': name,
'active': active,
'fee': undefined,
'precision': precision,
'limits': {
'amount': {
'min': this.safeFloat (details, 'min_size'),
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': undefined,
'max': undefined,
},
'withdraw': {
'min': this.safeFloat (details, 'min_withdrawal_amount'),
'max': undefined,
},
},
};
}
return result;
}
async fetchMarkets (params = {}) {
const response = await this.publicGetProducts (params);
//
// [
// {
// "id":"ZEC-BTC",
// "base_currency":"ZEC",
// "quote_currency":"BTC",
// "base_min_size":"0.01000000",
// "base_max_size":"1500.00000000",
// "quote_increment":"0.00000100",
// "base_increment":"0.00010000",
// "display_name":"ZEC/BTC",
// "min_market_funds":"0.001",
// "max_market_funds":"30",
// "margin_enabled":false,
// "post_only":false,
// "limit_only":false,
// "cancel_only":false,
// "trading_disabled":false,
// "status":"online",
// "status_message":""
// }
// ]
//
const result = [];
for (let i = 0; i < response.length; i++) {
const market = response[i];
@@ -182,10 +313,11 @@ module.exports = class coinbasepro extends Exchange {
'max': undefined,
};
const precision = {
'amount': this.precisionFromString (this.safeString (market, 'base_increment')),
'price': this.precisionFromString (this.safeString (market, 'quote_increment')),
'amount': this.safeFloat (market, 'base_increment'),
'price': this.safeFloat (market, 'quote_increment'),
};
const active = market['status'] === 'online';
const status = this.safeString (market, 'status');
const active = (status === 'online');
result.push (this.extend (this.fees['trading'], {
'id': id,
'symbol': symbol,
@@ -400,21 +532,8 @@ module.exports = class coinbasepro extends Exchange {
// }
//
const timestamp = this.parse8601 (this.safeString2 (trade, 'time', 'created_at'));
let symbol = undefined;
const marketId = this.safeString (trade, 'product_id');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market, '-');
let feeRate = undefined;
let feeCurrency = undefined;
let takerOrMaker = undefined;
@@ -485,14 +604,24 @@ module.exports = class coinbasepro extends Exchange {
return this.parseTrades (response, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// 1591514160,
// 0.02507,
// 0.02507,
// 0.02507,
// 0.02507,
// 0.02816506
// ]
//
return [
ohlcv[0] * 1000,
ohlcv[3],
ohlcv[2],
ohlcv[1],
ohlcv[4],
ohlcv[5],
this.safeTimestamp (ohlcv, 0),
this.safeFloat (ohlcv, 3),
this.safeFloat (ohlcv, 2),
this.safeFloat (ohlcv, 1),
this.safeFloat (ohlcv, 4),
this.safeFloat (ohlcv, 5),
];
}
@@ -513,12 +642,25 @@ module.exports = class coinbasepro extends Exchange {
request['end'] = this.iso8601 (this.sum ((limit - 1) * granularity * 1000, since));
}
const response = await this.publicGetProductsIdCandles (this.extend (request, params));
//
// [
// [1591514160,0.02507,0.02507,0.02507,0.02507,0.02816506],
// [1591514100,0.02507,0.02507,0.02507,0.02507,1.63830323],
// [1591514040,0.02505,0.02507,0.02505,0.02507,0.19918178]
// ]
//
return this.parseOHLCVs (response, market, timeframe, since, limit);
}
async fetchTime (params = {}) {
const response = await this.publicGetTime (params);
return this.parse8601 (this.safeString (response, 'iso'));
//
// {
// "iso":"2020-05-12T08:00:51.504Z",
// "epoch":1589270451.504
// }
//
return this.safeTimestamp (response, 'epoch');
}
parseOrderStatus (status) {
@@ -535,19 +677,8 @@ module.exports = class coinbasepro extends Exchange {
parseOrder (order, market = undefined) {
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
let symbol = undefined;
const marketId = this.safeString (order, 'product_id');
let quote = undefined;
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
market = this.safeMarket (marketId, market, '-');
const status = this.parseOrderStatus (this.safeString (order, 'status'));
const price = this.safeFloat (order, 'price');
const filled = this.safeFloat (order, 'filled_size');
@@ -565,8 +696,6 @@ module.exports = class coinbasepro extends Exchange {
let feeCurrencyCode = undefined;
if (market !== undefined) {
feeCurrencyCode = market['quote'];
} else if (quote !== undefined) {
feeCurrencyCode = quote;
}
fee = {
'cost': feeCost,
@@ -574,9 +703,6 @@ module.exports = class coinbasepro extends Exchange {
'rate': undefined,
};
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const id = this.safeString (order, 'id');
const type = this.safeString (order, 'type');
const side = this.safeString (order, 'side');
@@ -588,10 +714,12 @@ module.exports = class coinbasepro extends Exchange {
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'status': status,
'symbol': symbol,
'symbol': market['symbol'],
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -769,30 +897,49 @@ module.exports = class coinbasepro extends Exchange {
let currency = undefined;
let id = this.safeString (params, 'id'); // account id
if (id === undefined) {
if (code === undefined) {
throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency code argument if no account id specified in params');
if (code !== undefined) {
currency = this.currency (code);
const accountsByCurrencyCode = this.indexBy (this.accounts, 'currency');
const account = this.safeValue (accountsByCurrencyCode, code);
if (account === undefined) {
throw new ExchangeError (this.id + ' fetchTransactions() could not find account id for ' + code);
}
id = account['id'];
}
currency = this.currency (code);
const accountsByCurrencyCode = this.indexBy (this.accounts, 'currency');
const account = this.safeValue (accountsByCurrencyCode, code);
if (account === undefined) {
throw new ExchangeError (this.id + ' fetchTransactions() could not find account id for ' + code);
}
id = account['id'];
}
const request = {
'id': id,
};
const request = {};
if (id !== undefined) {
request['id'] = id;
}
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.privateGetAccountsIdTransfers (this.extend (request, params));
for (let i = 0; i < response.length; i++) {
response[i]['currency'] = code;
let response = undefined;
if (id === undefined) {
response = await this.privateGetTransfers (this.extend (request, params));
for (let i = 0; i < response.length; i++) {
const account_id = this.safeString (response[i], 'account_id');
const account = this.safeValue (this.accountsById, account_id);
const code = this.safeString (account, 'currency');
response[i]['currency'] = code;
}
} else {
response = await this.privateGetAccountsIdTransfers (this.extend (request, params));
for (let i = 0; i < response.length; i++) {
response[i]['currency'] = code;
}
}
return this.parseTransactions (response, currency, since, limit);
}
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
return this.fetchTransactions (code, since, limit, this.extend (params, { 'type': 'deposit' }));
}
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
return this.fetchTransactions (code, since, limit, this.extend (params, { 'type': 'withdraw' }));
}
parseTransactionStatus (transaction) {
const canceled = this.safeValue (transaction, 'canceled_at');
if (canceled) {
@@ -817,16 +964,23 @@ module.exports = class coinbasepro extends Exchange {
const updated = this.parse8601 (this.safeString (transaction, 'processed_at'));
const currencyId = this.safeString (transaction, 'currency');
const code = this.safeCurrencyCode (currencyId, currency);
const fee = undefined;
const status = this.parseTransactionStatus (transaction);
const amount = this.safeFloat (transaction, 'amount');
let type = this.safeString (transaction, 'type');
let address = this.safeString (details, 'crypto_address');
const tag = this.safeString (details, 'destination_tag');
address = this.safeString (transaction, 'crypto_address', address);
let fee = undefined;
if (type === 'withdraw') {
type = 'withdrawal';
address = this.safeString (details, 'sent_to_address', address);
const feeCost = this.safeFloat (details, 'fee');
if (feeCost !== undefined) {
fee = {
'cost': feeCost,
'code': code,
};
}
}
return {
'info': transaction,
@@ -869,7 +1023,7 @@ module.exports = class coinbasepro extends Exchange {
const signature = this.hmac (this.encode (what), secret, 'sha256', 'base64');
headers = {
'CB-ACCESS-KEY': this.apiKey,
'CB-ACCESS-SIGN': this.decode (signature),
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': nonce,
'CB-ACCESS-PASSPHRASE': this.password,
'Content-Type': 'application/json',
@@ -878,35 +1032,6 @@ module.exports = class coinbasepro extends Exchange {
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
async fetchDepositAddress (code, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
let accounts = this.safeValue (this.options, 'coinbaseAccounts');
if (accounts === undefined) {
accounts = await this.privateGetCoinbaseAccounts ();
this.options['coinbaseAccounts'] = accounts; // cache it
this.options['coinbaseAccountsByCurrencyId'] = this.indexBy (accounts, 'currency');
}
const currencyId = currency['id'];
const account = this.safeValue (this.options['coinbaseAccountsByCurrencyId'], currencyId);
if (account === undefined) {
// eslint-disable-next-line quotes
throw new InvalidAddress (this.id + " fetchDepositAddress() could not find currency code " + code + " with id = " + currencyId + " in this.options['coinbaseAccountsByCurrencyId']");
}
const request = {
'id': account['id'],
};
const response = await this.privateGetCoinbaseAccountsIdAddresses (this.extend (request, params));
const address = this.safeString (response, 'address');
const tag = this.safeString (response, 'destination_tag');
return {
'currency': code,
'address': this.checkAddress (address),
'tag': tag,
'info': response,
};
}
async createDepositAddress (code, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
@@ -15,12 +15,18 @@ module.exports = class coincheck extends Exchange {
'countries': [ 'JP', 'ID' ],
'rateLimit': 1500,
'has': {
'cancelOrder': true,
'CORS': false,
'fetchOpenOrders': true,
'createOrder': true,
'fetchBalance': true,
'fetchMyTrades': true,
'fetchOrderBook': true,
'fetchOpenOrders': true,
'fetchTicker': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27766464-3b5c3c74-5ed9-11e7-840e-31b32968e1da.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87182088-1d6d6380-c2ec-11ea-9c64-8ab9f9b289f5.jpg',
'api': 'https://coincheck.com/api',
'www': 'https://coincheck.com',
'doc': 'https://coincheck.com/documents/exchange/api',
@@ -178,18 +184,7 @@ module.exports = class coincheck extends Exchange {
}
const status = undefined;
const marketId = this.safeString (order, 'pair');
let symbol = undefined;
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
const [ baseId, quoteId ] = marketId.split ('_');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
const symbol = this.safeSymbol (marketId, market, '_');
return {
'id': id,
'clientOrderId': undefined,
@@ -201,9 +196,11 @@ module.exports = class coincheck extends Exchange {
'filled': filled,
'side': side,
'type': undefined,
'timeInForce': undefined,
'status': status,
'symbol': symbol,
'price': price,
'stopPrice': undefined,
'cost': cost,
'fee': undefined,
'info': order,
@@ -14,11 +14,18 @@ module.exports = class coinegg extends Exchange {
'name': 'CoinEgg',
'countries': [ 'CN', 'UK' ],
'has': {
'fetchOrder': true,
'fetchOrders': true,
'fetchOpenOrders': 'emulated',
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchMyTrades': false,
'fetchOpenOrders': 'emulated',
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTickers': false,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/36770310-adfa764e-1c5a-11e8-8e09-449daac3d2fb.jpg',
@@ -359,8 +366,10 @@ module.exports = class coinegg extends Exchange {
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': undefined,
'amount': amount,
'filled': filled,
@@ -393,7 +402,6 @@ module.exports = class coinegg extends Exchange {
'type': side,
'info': response,
}, market);
this.orders[id] = order;
return order;
}
@@ -417,7 +425,8 @@ module.exports = class coinegg extends Exchange {
'quote': market['quoteId'],
};
const response = await this.privatePostTradeViewRegionQuote (this.extend (request, params));
return this.parseOrder (response['data'], market);
const data = this.safeValue (response, 'data');
return this.parseOrder (data, market);
}
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -431,7 +440,8 @@ module.exports = class coinegg extends Exchange {
request['since'] = since / 1000;
}
const response = await this.privatePostTradeListRegionQuote (this.extend (request, params));
return this.parseOrders (response['data'], market, since, limit);
const data = this.safeValue (response, 'data', []);
return this.parseOrders (data, market, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -16,15 +16,22 @@ module.exports = class coinex extends Exchange {
'countries': [ 'CN' ],
'rateLimit': 1000,
'has': {
'fetchTickers': true,
'fetchOHLCV': true,
'fetchOrder': true,
'fetchOpenOrders': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchMyTrades': true,
'withdraw': true,
'fetchDeposits': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchWithdrawals': true,
'withdraw': true,
},
'timeframes': {
'1m': '1min',
@@ -42,7 +49,7 @@ module.exports = class coinex extends Exchange {
'1w': '1week',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/38046312-0b450aac-32c8-11e8-99ab-bc6b136b6cc7.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87182089-1e05fa00-c2ec-11ea-8da9-cc73b45abbbc.jpg',
'api': 'https://api.coinex.com',
'www': 'https://www.coinex.com',
'doc': 'https://github.com/coinexcom/coinex_exchange_api/wiki',
@@ -258,19 +265,16 @@ module.exports = class coinex extends Exchange {
const result = {};
for (let i = 0; i < marketIds.length; i++) {
const marketId = marketIds[i];
let symbol = marketId;
let market = undefined;
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
}
const ticker = {
const market = this.safeMarket (marketId);
const symbol = market['symbol'];
const ticker = this.parseTicker ({
'date': timestamp,
'ticker': tickers[marketId],
};
result[symbol] = this.parseTicker (ticker, market);
}, market);
ticker['symbol'] = symbol;
result[symbol] = ticker;
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchOrderBook (symbol, limit = 20, params = {}) {
@@ -298,11 +302,7 @@ module.exports = class coinex extends Exchange {
const price = this.safeFloat (trade, 'price');
const amount = this.safeFloat (trade, 'amount');
const marketId = this.safeString (trade, 'market');
market = this.safeValue (this.markets_by_id, marketId, market);
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market);
let cost = this.safeFloat (trade, 'deal_money');
if (!cost) {
cost = parseFloat (this.costToPrecision (symbol, price * amount));
@@ -346,14 +346,26 @@ module.exports = class coinex extends Exchange {
return this.parseTrades (response['data'], market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '5m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// 1591484400,
// "0.02505349",
// "0.02506988",
// "0.02507000",
// "0.02505304",
// "343.19716223",
// "8.6021323866383196",
// "ETHBTC"
// ]
//
return [
ohlcv[0] * 1000,
parseFloat (ohlcv[1]),
parseFloat (ohlcv[3]),
parseFloat (ohlcv[4]),
parseFloat (ohlcv[2]),
parseFloat (ohlcv[5]),
this.safeTimestamp (ohlcv, 0),
this.safeFloat (ohlcv, 1),
this.safeFloat (ohlcv, 3),
this.safeFloat (ohlcv, 4),
this.safeFloat (ohlcv, 2),
this.safeFloat (ohlcv, 5),
];
}
@@ -364,8 +376,23 @@ module.exports = class coinex extends Exchange {
'market': market['id'],
'type': this.timeframes[timeframe],
};
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.publicGetMarketKline (this.extend (request, params));
return this.parseOHLCVs (response['data'], market, timeframe, since, limit);
//
// {
// "code": 0,
// "data": [
// [1591484400, "0.02505349", "0.02506988", "0.02507000", "0.02505304", "343.19716223", "8.6021323866383196", "ETHBTC"],
// [1591484700, "0.02506990", "0.02508109", "0.02508109", "0.02506979", "91.59841581", "2.2972047780447000", "ETHBTC"],
// [1591485000, "0.02508106", "0.02507996", "0.02508106", "0.02507500", "65.15307697", "1.6340597822306000", "ETHBTC"],
// ],
// "message": "OK"
// }
//
const data = this.safeValue (response, 'data', []);
return this.parseOHLCVs (data, market, timeframe, since, limit);
}
async fetchBalance (params = {}) {
@@ -449,7 +476,7 @@ module.exports = class coinex extends Exchange {
const average = this.safeFloat (order, 'avg_price');
let symbol = undefined;
const marketId = this.safeString (order, 'market');
market = this.safeValue (this.markets_by_id, marketId);
market = this.safeMarket (marketId, market);
const feeCurrencyId = this.safeString (order, 'fee_asset');
let feeCurrency = this.safeCurrencyCode (feeCurrencyId);
if (market !== undefined) {
@@ -471,8 +498,10 @@ module.exports = class coinex extends Exchange {
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'average': average,
'amount': amount,
@@ -515,10 +544,8 @@ module.exports = class coinex extends Exchange {
request['price'] = this.priceToPrecision (symbol, price);
}
const response = await this[method] (this.extend (request, params));
const order = this.parseOrder (response['data'], market);
const id = order['id'];
this.orders[id] = order;
return order;
const data = this.safeValue (response, 'data');
return this.parseOrder (data, market);
}
async cancelOrder (id, symbol = undefined, params = {}) {
@@ -529,7 +556,8 @@ module.exports = class coinex extends Exchange {
'market': market['id'],
};
const response = await this.privateDeleteOrderPending (this.extend (request, params));
return this.parseOrder (response['data'], market);
const data = this.safeValue (response, 'data');
return this.parseOrder (data, market);
}
async fetchOrder (id, symbol = undefined, params = {}) {
@@ -569,7 +597,8 @@ module.exports = class coinex extends Exchange {
// "message": "Ok"
// }
//
return this.parseOrder (response['data'], market);
const data = this.safeValue (response, 'data');
return this.parseOrder (data, market);
}
async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -588,7 +617,9 @@ module.exports = class coinex extends Exchange {
}
const method = 'privateGetOrder' + this.capitalize (status);
const response = await this[method] (this.extend (request, params));
return this.parseOrders (response['data']['data'], market, since, limit);
const data = this.safeValue (response, 'data');
const orders = this.safeValue (data, 'data', []);
return this.parseOrders (orders, market, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -614,7 +645,9 @@ module.exports = class coinex extends Exchange {
request['market'] = market['id'];
}
const response = await this.privateGetOrderUserDeals (this.extend (request, params));
return this.parseTrades (response['data']['data'], market, since, limit);
const data = this.safeValue (response, 'data');
const trades = this.safeValue (data, 'data', []);
return this.parseTrades (trades, market, since, limit);
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
@@ -628,7 +661,7 @@ module.exports = class coinex extends Exchange {
'coin_type': currency['id'],
'coin_address': address, // must be authorized, inter-user transfer by a registered mobile phone number or an email address is supported
'actual_amount': parseFloat (amount), // the actual amount without fees, https://www.coinex.com/fees
'transfer_method': '1', // '1' = normal onchain transfer, '2' = internal local transfer from one user to another
'transfer_method': 'onchain', // onchain, local
};
const response = await this.privatePostBalanceCoinWithdraw (this.extend (request, params));
//
@@ -768,48 +801,46 @@ module.exports = class coinex extends Exchange {
//
// {
// "code": 0,
// "data": [
// {
// "actual_amount": "1.00000000",
// "amount": "1.00000000",
// "coin_address": "1KAv3pazbTk2JnQ5xTo6fpKK7p1it2RzD4",
// "coin_type": "BCH",
// "coin_withdraw_id": 206,
// "confirmations": 0,
// "create_time": 1524228297,
// "status": "audit",
// "tx_fee": "0",
// "tx_id": ""
// },
// {
// "actual_amount": "0.10000000",
// "amount": "0.10000000",
// "coin_address": "15sr1VdyXQ6sVLqeJUJ1uPzLpmQtgUeBSB",
// "coin_type": "BCH",
// "coin_withdraw_id": 203,
// "confirmations": 11,
// "create_time": 1515806440,
// "status": "finish",
// "tx_fee": "0",
// "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6"
// },
// {
// "actual_amount": "0.00100000",
// "amount": "0.00100000",
// "coin_address": "1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF",
// "coin_type": "BCH",
// "coin_withdraw_id": 27,
// "confirmations": 0,
// "create_time": 1513933541,
// "status": "cancel",
// "tx_fee": "0",
// "tx_id": ""
// }
// ],
// "message": "Ok"
// "data": {
// "has_next": true,
// "curr_page": 1,
// "count": 10,
// "data": [
// {
// "coin_withdraw_id": 203,
// "create_time": 1513933541,
// "actual_amount": "0.00100000",
// "actual_amount_display": "***",
// "amount": "0.00100000",
// "amount_display": "******",
// "coin_address": "1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF",
// "app_coin_address_display": "**********",
// "coin_address_display": "****************",
// "add_explorer": "https://explorer.viawallet.com/btc/address/1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF",
// "coin_type": "BTC",
// "confirmations": 6,
// "explorer": "https://explorer.viawallet.com/btc/tx/1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF",
// "fee": "0",
// "remark": "",
// "smart_contract_name": "BTC",
// "status": "finish",
// "status_display": "finish",
// "transfer_method": "onchain",
// "tx_fee": "0",
// "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6"
// }, /* many more data points */
// ],
// "total": ***,
// "total_page":***
// },
// "message": "Success"
// }
//
return this.parseTransactions (response['data'], currency, since, limit);
let data = this.safeValue (response, 'data');
if (!Array.isArray (data)) {
data = this.safeValue (data, 'data', []);
}
return this.parseTransactions (data, currency, since, limit);
}
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
@@ -852,7 +883,11 @@ module.exports = class coinex extends Exchange {
// "message": "Ok"
// }
//
return this.parseTransactions (response['data'], currency, since, limit);
let data = this.safeValue (response, 'data');
if (!Array.isArray (data)) {
data = this.safeValue (data, 'data', []);
}
return this.parseTransactions (data, currency, since, limit);
}
nonce () {
@@ -16,9 +16,17 @@ module.exports = class coinfalcon extends Exchange {
'rateLimit': 1000,
'version': 'v1',
'has': {
'fetchTickers': true,
'fetchOpenOrders': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/41822275-ed982188-77f5-11e8-92bb-496bcd14ca52.jpg',
@@ -109,14 +117,8 @@ module.exports = class coinfalcon extends Exchange {
}
parseTicker (ticker, market = undefined) {
if (market === undefined) {
const marketId = this.safeString (ticker, 'name');
market = this.safeValue (this.markets_by_id, marketId, market);
}
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
}
const marketId = this.safeString (ticker, 'name');
const symbol = this.safeSymbol (marketId, market, '-');
const timestamp = this.milliseconds ();
const last = parseFloat (ticker['last_price']);
return {
@@ -159,7 +161,7 @@ module.exports = class coinfalcon extends Exchange {
const symbol = ticker['symbol'];
result[symbol] = ticker;
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -169,7 +171,8 @@ module.exports = class coinfalcon extends Exchange {
'level': '3',
};
const response = await this.publicGetMarketsMarketOrders (this.extend (request, params));
return this.parseOrderBook (response['data'], undefined, 'bids', 'asks', 'price', 'size');
const data = this.safeValue (response, 'data', {});
return this.parseOrderBook (data, undefined, 'bids', 'asks', 'price', 'size');
}
parseTrade (trade, market = undefined) {
@@ -228,7 +231,8 @@ module.exports = class coinfalcon extends Exchange {
request['limit'] = limit;
}
const response = await this.privateGetUserTrades (this.extend (request, params));
return this.parseTrades (response['data'], market, since, limit);
const data = this.safeValue (response, 'data', []);
return this.parseTrades (data, market, since, limit);
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
@@ -241,7 +245,8 @@ module.exports = class coinfalcon extends Exchange {
request['since'] = this.iso8601 (since);
}
const response = await this.publicGetMarketsMarketTrades (this.extend (request, params));
return this.parseTrades (response['data'], market, since, limit);
const data = this.safeValue (response, 'data', []);
return this.parseTrades (data, market, since, limit);
}
async fetchBalance (params = {}) {
@@ -292,16 +297,8 @@ module.exports = class coinfalcon extends Exchange {
// "created_at":"2018-01-12T21:14:06.747828Z"
// }
//
if (market === undefined) {
const marketId = this.safeString (order, 'market');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
}
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
}
const marketId = this.safeString (order, 'market');
const symbol = this.safeSymbol (marketId, market, '-');
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
const price = this.safeFloat (order, 'price');
const amount = this.safeFloat (order, 'size');
@@ -310,10 +307,10 @@ module.exports = class coinfalcon extends Exchange {
let cost = undefined;
if (amount !== undefined) {
if (filled !== undefined) {
remaining = parseFloat (this.amountToPrecision (symbol, amount - filled));
remaining = Math.max (0, amount - filled);
}
if (price !== undefined) {
cost = parseFloat (this.priceToPrecision (symbol, filled * price));
cost = filled * price;
}
}
const status = this.parseOrderStatus (this.safeString (order, 'status'));
@@ -331,8 +328,10 @@ module.exports = class coinfalcon extends Exchange {
'status': status,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -360,10 +359,8 @@ module.exports = class coinfalcon extends Exchange {
}
request['operation_type'] = type + '_order';
const response = await this.privatePostUserOrders (this.extend (request, params));
const order = this.parseOrder (response['data'], market);
const id = order['id'];
this.orders[id] = order;
return order;
const data = this.safeValue (response, 'data', {});
return this.parseOrder (data, market);
}
async cancelOrder (id, symbol = undefined, params = {}) {
@@ -373,7 +370,8 @@ module.exports = class coinfalcon extends Exchange {
};
const response = await this.privateDeleteUserOrdersId (this.extend (request, params));
const market = this.market (symbol);
return this.parseOrder (response['data'], market);
const data = this.safeValue (response, 'data', {});
return this.parseOrder (data, market);
}
async fetchOrder (id, symbol = undefined, params = {}) {
@@ -382,21 +380,26 @@ module.exports = class coinfalcon extends Exchange {
'id': id,
};
const response = await this.privateGetUserOrdersId (this.extend (request, params));
return this.parseOrder (response['data']);
const data = this.safeValue (response, 'data', {});
return this.parseOrder (data);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
let market = undefined;
if (symbol !== undefined) {
request['market'] = this.marketId (symbol);
market = this.market (symbol);
request['market'] = market['id'];
}
if (since !== undefined) {
request['since_time'] = this.iso8601 (this.milliseconds ());
request['since_time'] = this.iso8601 (since);
}
// TODO: test status=all if it works for closed orders too
const response = await this.privateGetUserOrders (this.extend (request, params));
return this.parseOrders (response['data']);
const data = this.safeValue (response, 'data', []);
const orders = this.filterByArray (data, 'status', [ 'pending', 'open', 'partially_filled' ], false);
return this.parseOrders (orders, market, since, limit);
}
nonce () {
@@ -15,11 +15,18 @@ module.exports = class coinfloor extends Exchange {
'rateLimit': 1000,
'countries': [ 'UK' ],
'has': {
'cancelOrder': true,
'CORS': false,
'createOrder': true,
'fetchBalance': true,
'fetchLedger': true,
'fetchOpenOrders': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/28246081-623fc164-6a1c-11e7-913f-bac0d5576c90.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87153925-ef265e80-c2c0-11ea-91b5-020c804b90e0.jpg',
'api': 'https://webapi.coinfloor.co.uk/bist',
'www': 'https://www.coinfloor.co.uk',
'doc': [
@@ -399,7 +406,27 @@ module.exports = class coinfloor extends Exchange {
request['price'] = price;
request['amount'] = amount;
}
return await this[method] (this.extend (request, params));
//
// {
// "id":31950584,
// "datetime":"2020-05-21 08:38:18",
// "type":1,
// "price":"9100",
// "amount":"0.0026"
// }
//
const response = await this[method] (this.extend (request, params));
const timestamp = this.parse8601 (this.safeString (response, 'datetime'));
return {
'id': this.safeString (response, 'id'),
'clientOrderId': undefined,
'datetime': this.iso8601 (timestamp),
'timestamp': timestamp,
'type': type,
'price': this.safeFloat (response, 'price'),
'remaining': this.safeFloat (response, 'amount'),
'info': response,
};
}
async cancelOrder (id, symbol = undefined, params = {}) {
@@ -452,11 +479,13 @@ module.exports = class coinfloor extends Exchange {
'status': status,
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'amount': amount,
'stopPrice': undefined,
'amount': undefined,
'filled': undefined,
'remaining': undefined,
'remaining': amount,
'cost': cost,
'fee': undefined,
'average': undefined,
@@ -510,7 +539,7 @@ module.exports = class coinfloor extends Exchange {
const nonce = this.nonce ();
body = this.urlencode (this.extend ({ 'nonce': nonce }, query));
const auth = this.uid + '/' + this.apiKey + ':' + this.password;
const signature = this.decode (this.stringToBase64 (this.encode (auth)));
const signature = this.decode (this.stringToBase64 (auth));
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + signature,
@@ -15,8 +15,15 @@ module.exports = class coingi extends Exchange {
'rateLimit': 1000,
'countries': [ 'PA', 'BG', 'CN', 'US' ], // Panama, Bulgaria, China, US
'has': {
'cancelOrder': true,
'CORS': false,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
},
'urls': {
'referral': 'https://www.coingi.com/?r=XTPPMC',
@@ -226,7 +233,7 @@ module.exports = class coingi extends Exchange {
}
result[symbol] = this.parseTicker (ticker, market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -250,13 +257,7 @@ module.exports = class coingi extends Exchange {
const timestamp = this.safeInteger (trade, 'timestamp');
const id = this.safeString (trade, 'id');
const marketId = this.safeString (trade, 'currencyPair');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market);
return {
'id': id,
'info': trade,
@@ -16,23 +16,25 @@ module.exports = class coinmarketcap extends Exchange {
'version': 'v1',
'countries': [ 'US' ],
'has': {
'CORS': true,
'privateAPI': false,
'createOrder': false,
'createMarketOrder': false,
'createLimitOrder': false,
'cancelOrder': false,
'CORS': true,
'createLimitOrder': false,
'createMarketOrder': false,
'createOrder': false,
'editOrder': false,
'privateAPI': false,
'fetchBalance': false,
'fetchOrderBook': false,
'fetchL2OrderBook': false,
'fetchOHLCV': false,
'fetchTrades': false,
'fetchTickers': true,
'fetchCurrencies': true,
'fetchL2OrderBook': false,
'fetchMarkets': true,
'fetchOHLCV': false,
'fetchOrderBook': false,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': false,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/28244244-9be6312a-69ed-11e7-99c1-7c1797275265.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87182086-1cd4cd00-c2ec-11ea-9ec4-d0cf2a2abf62.jpg',
'api': {
'public': 'https://api.coinmarketcap.com',
'files': 'https://files.coinmarketcap.com',
@@ -96,24 +98,29 @@ module.exports = class coinmarketcap extends Exchange {
'ACChain': 'ACChain',
'AdCoin': 'AdCoin',
'BatCoin': 'BatCoin',
'BigONE Token': 'BigONE Token', // conflict with Harmony (ONE)
'Bitgem': 'Bitgem',
'BlazeCoin': 'BlazeCoin',
'BlockCAT': 'BlockCAT',
'Blocktrade Token': 'Blocktrade Token',
'BOX Token': 'BOX Token', // conflict with BOX (ContentBox)
'Catcoin': 'Catcoin',
'CanYaCoin': 'CanYaCoin', // conflict with CAN (Content and AD Network)
'CryptoBossCoin': 'CryptoBossCoin', // conflict with CBC (CashBet Coin)
'Comet': 'Comet', // conflict with CMT (CyberMiles)
'CPChain': 'CPChain',
'CrowdCoin': 'CrowdCoin', // conflict with CRC CryCash
'Cryptaur': 'Cryptaur', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081
'Cubits': 'Cubits', // conflict with QBT (Qbao)
'DAO.Casino': 'DAO.Casino', // conflict with BET (BetaCoin)
'DefiBox': 'DefiBox', // conflict with BOX (ContentBox)
'E-Dinar Coin': 'E-Dinar Coin', // conflict with EDR Endor Protocol and EDRCoin
'EDRcoin': 'EDRcoin', // conflict with EDR Endor Protocol and E-Dinar Coin
'ENTCash': 'ENTCash', // conflict with ENT (Eternity)
'FairCoin': 'FairCoin', // conflict with FAIR (FairGame) https://github.com/ccxt/ccxt/pull/5865
'Fabric Token': 'Fabric Token',
// 'GET Protocol': 'GET Protocol',
'GHOSTPRISM': 'GHOSTPRISM', // conflict with GHOST
'Global Tour Coin': 'Global Tour Coin', // conflict with GTC (Game.com)
'GuccioneCoin': 'GuccioneCoin', // conflict with GCC (Global Cryptocurrency)
'HarmonyCoin': 'HarmonyCoin', // conflict with HMC (Hi Mutual Society)
@@ -123,14 +130,18 @@ module.exports = class coinmarketcap extends Exchange {
'Huncoin': 'Huncoin', // conflict with HNC (Helleniccoin)
'iCoin': 'iCoin',
'Infinity Economics': 'Infinity Economics', // conflict with XIN (Mixin)
'IQ.cash': 'IQ.cash', // conflict with IQ (Everipedia)
'KingN Coin': 'KingN Coin', // conflict with KNC (Kyber Network)
'LiteBitcoin': 'LiteBitcoin', // conflict with LBTC (LightningBitcoin)
'Maggie': 'Maggie',
'Menlo One': 'Menlo One', // conflict with Harmony (ONE)
'Mobilian Coin': 'Mobilian Coin', // conflict with Membrana (MBN)
'Monarch': 'Monarch', // conflict with MyToken (MT)
'MTC Mesh Network': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025
'IOTA': 'IOTA', // a special case, most exchanges list it as IOTA, therefore we change just the Coinmarketcap instead of changing them all
'NetCoin': 'NetCoin',
'PCHAIN': 'PCHAIN', // conflict with PAI (Project Pai)
'Penta': 'Penta', // conflict with PNT (pNetwork)
'Plair': 'Plair', // conflict with PLA (PLANET)
'PlayChip': 'PlayChip', // conflict with PLA (PLANET)
'Polcoin': 'Polcoin',
@@ -139,9 +150,11 @@ module.exports = class coinmarketcap extends Exchange {
// https://github.com/ccxt/ccxt/issues/6081
// https://github.com/ccxt/ccxt/issues/3365
// https://github.com/ccxt/ccxt/issues/2873
'TerraCredit': 'TerraCredit', // conflict with CREDIT (PROXI)
'Themis': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol)
'Menlo One': 'Menlo One', // conflict with Harmony (ONE)
'BigONE Token': 'BigONE Token', // conflict with Harmony (ONE)
'UNI COIN': 'UNI COIN', // conflict with UNI (Uniswap)
'UNICORN Token': 'UNICORN Token', // conflict with UNI (Uniswap)
'Universe': 'Universe', // conflict with UNI (Uniswap)
};
return this.safeValue (currencies, name, base);
}
@@ -15,18 +15,22 @@ module.exports = class coinmate extends Exchange {
'countries': [ 'GB', 'CZ', 'EU' ], // UK, Czech Republic
'rateLimit': 1000,
'has': {
'CORS': true,
'fetchBalance': true,
'fetchOrders': true,
'fetchOrder': true,
'fetchMyTrades': true,
'fetchTransactions': true,
'fetchOpenOrders': true,
'createOrder': true,
'cancelOrder': true,
'CORS': true,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTrades': true,
'fetchTransactions': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27811229-c1efb510-606c-11e7-9a36-84ba2ce412d8.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87460806-1c9f3f00-c616-11ea-8c46-a77018a8f3f4.jpg',
'api': 'https://coinmate.io/api',
'www': 'https://coinmate.io',
'fees': 'https://coinmate.io/fees',
@@ -393,7 +397,7 @@ module.exports = class coinmate extends Exchange {
'status': status,
'fee': {
'cost': fee,
'currency': currency,
'currency': code,
},
'info': item,
};
@@ -447,25 +451,8 @@ module.exports = class coinmate extends Exchange {
// "tradeType":"BUY"
// }
//
let symbol = undefined;
const marketId = this.safeString (trade, 'currencyPair');
let quote = undefined;
if (marketId !== undefined) {
if (marketId in this.markets_by_id[marketId]) {
market = this.markets_by_id[marketId];
quote = market['quote'];
} else {
const [ baseId, quoteId ] = marketId.split ('_');
const base = this.safeCurrencyCode (baseId);
quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if (symbol === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
}
market = this.safeMarket (marketId, market, '_');
const price = this.safeFloat (trade, 'price');
const amount = this.safeFloat (trade, 'amount');
let cost = undefined;
@@ -484,7 +471,7 @@ module.exports = class coinmate extends Exchange {
if (feeCost !== undefined) {
fee = {
'cost': feeCost,
'currency': quote,
'currency': market['quote'],
};
}
let takerOrMaker = this.safeString (trade, 'feeType');
@@ -494,7 +481,7 @@ module.exports = class coinmate extends Exchange {
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': symbol,
'symbol': market['symbol'],
'type': type,
'side': side,
'order': orderId,
@@ -622,14 +609,17 @@ module.exports = class coinmate extends Exchange {
const timestamp = this.safeInteger (order, 'timestamp');
const side = this.safeStringLower (order, 'type');
const price = this.safeFloat (order, 'price');
const amount = this.safeFloat2 (order, 'originalAmount', 'amount');
const remaining = this.safeFloat (order, 'remainingAmount', amount);
const amount = this.safeFloat (order, 'originalAmount');
let remaining = this.safeFloat (order, 'remainingAmount');
if (remaining === undefined) {
remaining = this.safeFloat (order, 'amount');
}
let status = this.parseOrderStatus (this.safeString (order, 'status'));
const type = this.parseOrderType (this.safeString (order, 'orderTradeType'));
let filled = undefined;
let cost = undefined;
if ((amount !== undefined) && (remaining !== undefined)) {
filled = Math.min (amount - remaining, 0);
filled = Math.max (amount - remaining, 0);
if (remaining === 0) {
status = 'closed';
}
@@ -638,22 +628,10 @@ module.exports = class coinmate extends Exchange {
}
}
const average = this.safeFloat (order, 'avgPrice');
let symbol = undefined;
const marketId = this.safeString (order, 'currencyPair');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('_');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market, '_');
const clientOrderId = this.safeString (order, 'clientOrderId');
const stopPrice = this.safeFloat (order, 'stopPrice');
return {
'id': id,
'clientOrderId': clientOrderId,
@@ -662,8 +640,10 @@ module.exports = class coinmate extends Exchange {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': stopPrice,
'amount': amount,
'cost': cost,
'average': average,
@@ -0,0 +1,681 @@
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { BadSymbol, BadRequest, ExchangeError, ArgumentsRequired, OrderNotFound, OnMaintenance } = require ('./base/errors');
// ---------------------------------------------------------------------------
module.exports = class coinone extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'coinone',
'name': 'CoinOne',
'countries': [ 'KR' ], // Korea
// 'enableRateLimit': false,
'rateLimit': 667,
'version': 'v2',
'has': {
'cancelOrder': true,
'CORS': false,
'createMarketOrder': false,
'createOrder': true,
'fetchBalance': true,
'fetchCurrencies': false,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
// https://github.com/ccxt/ccxt/pull/7067
// the endpoint that should return closed orders actually returns trades
'fetchClosedOrders': false,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/38003300-adc12fba-323f-11e8-8525-725f53c4a659.jpg',
'api': 'https://api.coinone.co.kr',
'www': 'https://coinone.co.kr',
'doc': 'https://doc.coinone.co.kr',
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
},
'api': {
'public': {
'get': [
'orderbook/',
'trades/',
'ticker/',
],
},
'private': {
'post': [
'account/btc_deposit_address/',
'account/balance/',
'account/daily_balance/',
'account/user_info/',
'account/virtual_account/',
'order/cancel_all/',
'order/cancel/',
'order/limit_buy/',
'order/limit_sell/',
'order/complete_orders/',
'order/limit_orders/',
'order/order_info/',
'transaction/auth_number/',
'transaction/history/',
'transaction/krw/history/',
'transaction/btc/',
'transaction/coin/',
],
},
},
'fees': {
'trading': {
'tierBased': false,
'percentage': true,
'taker': 0.002,
'maker': 0.002,
},
},
'precision': {
'price': 4,
'amount': 4,
'cost': 8,
},
'exceptions': {
'405': OnMaintenance, // {"errorCode":"405","status":"maintenance","result":"error"}
'104': OrderNotFound, // {"errorCode":"104","errorMsg":"Order id is not exist","result":"error"}
'108': BadSymbol, // {"errorCode":"108","errorMsg":"Unknown CryptoCurrency","result":"error"}
'107': BadRequest, // {"errorCode":"107","errorMsg":"Parameter error","result":"error"}
},
});
}
async fetchMarkets (params = {}) {
const request = {
'currency': 'all',
};
const response = await this.publicGetTicker (request);
const result = [];
const quoteId = 'krw';
const quote = this.safeCurrencyCode (quoteId);
const baseIds = Object.keys (response);
for (let i = 0; i < baseIds.length; i++) {
const baseId = baseIds[i];
const ticker = this.safeValue (response, baseId, {});
const currency = this.safeValue (ticker, 'currency');
if (currency === undefined) {
continue;
}
const base = this.safeCurrencyCode (baseId);
result.push ({
'id': baseId,
'symbol': base + '/' + quote,
'base': base,
'quote': quote,
'baseId': baseId,
'quoteId': quoteId,
'active': true,
});
}
return result;
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const response = await this.privatePostAccountBalance (params);
const result = { 'info': response };
const balances = this.omit (response, [
'errorCode',
'result',
'normalWallets',
]);
const currencyIds = Object.keys (balances);
for (let i = 0; i < currencyIds.length; i++) {
const currencyId = currencyIds[i];
const balance = balances[currencyId];
const code = this.safeCurrencyCode (currencyId);
const account = this.account ();
account['free'] = this.safeFloat (balance, 'avail');
account['total'] = this.safeFloat (balance, 'balance');
result[code] = account;
}
return this.parseBalance (result);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'currency': market['id'],
'format': 'json',
};
const response = await this.publicGetOrderbook (this.extend (request, params));
const timestamp = this.safeTimestamp (response, 'timestamp');
return this.parseOrderBook (response, timestamp, 'bid', 'ask', 'price', 'qty');
}
async fetchTickers (symbols = undefined, params = {}) {
await this.loadMarkets ();
const request = {
'currency': 'all',
'format': 'json',
};
const response = await this.publicGetTicker (this.extend (request, params));
const result = {};
const ids = Object.keys (response);
const timestamp = this.safeTimestamp (response, 'timestamp');
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
let symbol = id;
let market = undefined;
if (id in this.markets_by_id) {
market = this.markets_by_id[id];
symbol = market['symbol'];
const ticker = response[id];
result[symbol] = this.parseTicker (ticker, market);
result[symbol]['timestamp'] = timestamp;
}
}
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'currency': market['id'],
'format': 'json',
};
const response = await this.publicGetTicker (this.extend (request, params));
return this.parseTicker (response, market);
}
parseTicker (ticker, market = undefined) {
const timestamp = this.safeTimestamp (ticker, 'timestamp');
const first = this.safeFloat (ticker, 'first');
const last = this.safeFloat (ticker, 'last');
let average = undefined;
if (first !== undefined && last !== undefined) {
average = this.sum (first, last) / 2;
}
const previousClose = this.safeFloat (ticker, 'yesterday_last');
let change = undefined;
let percentage = undefined;
if (last !== undefined && previousClose !== undefined) {
change = last - previousClose;
if (previousClose !== 0) {
percentage = change / previousClose * 100;
}
}
const symbol = (market !== undefined) ? market['symbol'] : undefined;
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeFloat (ticker, 'high'),
'low': this.safeFloat (ticker, 'low'),
'bid': undefined,
'bidVolume': undefined,
'ask': undefined,
'askVolume': undefined,
'vwap': undefined,
'open': first,
'close': last,
'last': last,
'previousClose': previousClose,
'change': change,
'percentage': percentage,
'average': average,
'baseVolume': this.safeFloat (ticker, 'volume'),
'quoteVolume': undefined,
'info': ticker,
};
}
parseTrade (trade, market = undefined) {
//
// fetchTrades (public)
//
// {
// "timestamp": "1416893212",
// "price": "420000.0",
// "qty": "0.1",
// "is_ask": "1"
// }
//
// fetchMyTrades (private)
//
// {
// "timestamp": "1416561032",
// "price": "419000.0",
// "type": "bid",
// "qty": "0.001",
// "feeRate": "-0.0015",
// "fee": "-0.0000015",
// "orderId": "E84A1AC2-8088-4FA0-B093-A3BCDB9B3C85"
// }
//
const timestamp = this.safeTimestamp (trade, 'timestamp');
const symbol = (market !== undefined) ? market['symbol'] : undefined;
const is_ask = this.safeString (trade, 'is_ask');
let side = this.safeString (trade, 'type');
if (is_ask !== undefined) {
if (is_ask === '1') {
side = 'sell';
} else if (is_ask === '0') {
side = 'buy';
}
} else {
if (side === 'ask') {
side = 'sell';
} else if (side === 'bid') {
side = 'buy';
}
}
const price = this.safeFloat (trade, 'price');
const amount = this.safeFloat (trade, 'qty');
let cost = undefined;
if (price !== undefined) {
if (amount !== undefined) {
cost = price * amount;
}
}
const orderId = this.safeString (trade, 'orderId');
let feeCost = this.safeFloat (trade, 'fee');
let fee = undefined;
if (feeCost !== undefined) {
feeCost = Math.abs (feeCost);
let feeRate = this.safeFloat (trade, 'feeRate');
feeRate = Math.abs (feeRate);
let feeCurrencyCode = undefined;
if (market !== undefined) {
feeCurrencyCode = (side === 'sell') ? market['quote'] : market['base'];
}
fee = {
'cost': feeCost,
'currency': feeCurrencyCode,
'rate': feeRate,
};
}
return {
'id': this.safeString (trade, 'id'),
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'order': orderId,
'symbol': symbol,
'type': undefined,
'side': side,
'takerOrMaker': undefined,
'price': price,
'amount': amount,
'cost': cost,
'fee': fee,
};
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'currency': market['id'],
'format': 'json',
};
const response = await this.publicGetTrades (this.extend (request, params));
//
// {
// "result": "success",
// "errorCode": "0",
// "timestamp": "1416895635",
// "currency": "btc",
// "completeOrders": [
// {
// "timestamp": "1416893212",
// "price": "420000.0",
// "qty": "0.1",
// "is_ask": "1"
// }
// ]
// }
//
const completeOrders = this.safeValue (response, 'completeOrders', []);
return this.parseTrades (completeOrders, market, since, limit);
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
if (type !== 'limit') {
throw new ExchangeError (this.id + ' allows limit orders only');
}
await this.loadMarkets ();
const request = {
'price': price,
'currency': this.marketId (symbol),
'qty': amount,
};
const method = 'privatePostOrder' + this.capitalize (type) + this.capitalize (side);
const response = await this[method] (this.extend (request, params));
//
// {
// "result": "success",
// "errorCode": "0",
// "orderId": "8a82c561-40b4-4cb3-9bc0-9ac9ffc1d63b"
// }
//
return this.parseOrder (response);
}
async fetchOrder (id, symbol = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument');
}
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'order_id': id,
'currency': market['id'],
};
const response = await this.privatePostOrderOrderInfo (this.extend (request, params));
//
// {
// "result": "success",
// "errorCode": "0",
// "status": "live",
// "info": {
// "orderId": "32FF744B-D501-423A-8BA1-05BB6BE7814A",
// "currency": "BTC",
// "type": "bid",
// "price": "2922000.0",
// "qty": "115.4950",
// "remainQty": "45.4950",
// "feeRate": "0.0003",
// "fee": "0",
// "timestamp": "1499340941"
// }
// }
//
const info = this.safeValue (response, 'info', {});
info['status'] = this.safeString (info, 'status');
return this.parseOrder (info, market);
}
parseOrderStatus (status) {
const statuses = {
'live': 'open',
'partially_filled': 'open',
'filled': 'closed',
};
return this.safeString (statuses, status, status);
}
parseOrder (order, market = undefined) {
//
// createOrder
//
// {
// "result": "success",
// "errorCode": "0",
// "orderId": "8a82c561-40b4-4cb3-9bc0-9ac9ffc1d63b"
// }
//
// fetchOrder
//
// {
// "status": "live", // injected in fetchOrder
// "orderId": "32FF744B-D501-423A-8BA1-05BB6BE7814A",
// "currency": "BTC",
// "type": "bid",
// "price": "2922000.0",
// "qty": "115.4950",
// "remainQty": "45.4950",
// "feeRate": "0.0003",
// "fee": "0",
// "timestamp": "1499340941"
// }
//
// fetchOpenOrders
//
// {
// "index": "0",
// "orderId": "68665943-1eb5-4e4b-9d76-845fc54f5489",
// "timestamp": "1449037367",
// "price": "444000.0",
// "qty": "0.3456",
// "type": "ask",
// "feeRate": "-0.0015"
// }
//
const id = this.safeString (order, 'orderId');
const price = this.safeFloat (order, 'price');
const timestamp = this.safeTimestamp (order, 'timestamp');
let side = this.safeString (order, 'type');
if (side === 'ask') {
side = 'sell';
} else if (side === 'bid') {
side = 'buy';
}
const remaining = this.safeFloat (order, 'remainQty');
let filled = undefined;
const amount = this.safeFloat (order, 'qty');
let status = this.safeString (order, 'status');
// https://github.com/ccxt/ccxt/pull/7067
if (status === 'live') {
if ((remaining !== undefined) && (amount !== undefined)) {
if (remaining < amount) {
status = 'canceled';
}
}
}
if ((remaining !== undefined) && (amount !== undefined)) {
filled = Math.max (amount - remaining);
}
status = this.parseOrderStatus (status);
let cost = undefined;
if ((price !== undefined) && (filled !== undefined)) {
cost = price * filled;
}
let symbol = undefined;
let base = undefined;
let quote = undefined;
const marketId = this.safeStringLower (order, 'currency');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
base = this.safeCurrencyCode (marketId);
quote = 'KRW';
symbol = base + '/' + quote;
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
base = market['base'];
quote = market['quote'];
}
let fee = undefined;
const feeCost = this.safeFloat (order, 'fee');
if (feeCost !== undefined) {
const feeCurrencyCode = (side === 'sell') ? quote : base;
fee = {
'cost': feeCost,
'rate': this.safeFloat (order, 'feeRate'),
'currency': feeCurrencyCode,
};
}
return {
'info': order,
'id': id,
'clientOrderId': undefined,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'average': undefined,
'amount': amount,
'filled': filled,
'remaining': amount,
'status': status,
'fee': fee,
'trades': undefined,
};
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
// The returned amount might not be same as the ordered amount. If an order is partially filled, the returned amount means the remaining amount.
// For the same reason, the returned amount and remaining are always same, and the returned filled and cost are always zero.
if (symbol === undefined) {
throw new ExchangeError (this.id + ' allows fetching closed orders with a specific symbol');
}
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'currency': market['id'],
};
const response = await this.privatePostOrderLimitOrders (this.extend (request, params));
//
// {
// "result": "success",
// "errorCode": "0",
// "limitOrders": [
// {
// "index": "0",
// "orderId": "68665943-1eb5-4e4b-9d76-845fc54f5489",
// "timestamp": "1449037367",
// "price": "444000.0",
// "qty": "0.3456",
// "type": "ask",
// "feeRate": "-0.0015"
// }
// ]
// }
//
const limitOrders = this.safeValue (response, 'limitOrders', []);
return this.parseOrders (limitOrders, market, since, limit);
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument');
}
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'currency': market['id'],
};
const response = await this.privatePostOrderCompleteOrders (this.extend (request, params));
//
// despite the name of the endpoint it returns trades which may have a duplicate orderId
// https://github.com/ccxt/ccxt/pull/7067
//
// {
// "result": "success",
// "errorCode": "0",
// "completeOrders": [
// {
// "timestamp": "1416561032",
// "price": "419000.0",
// "type": "bid",
// "qty": "0.001",
// "feeRate": "-0.0015",
// "fee": "-0.0000015",
// "orderId": "E84A1AC2-8088-4FA0-B093-A3BCDB9B3C85"
// }
// ]
// }
//
const completeOrders = this.safeValue (response, 'completeOrders', []);
return this.parseTrades (completeOrders, market, since, limit);
}
async cancelOrder (id, symbol = undefined, params = {}) {
if (symbol === undefined) {
// eslint-disable-next-line quotes
throw new ArgumentsRequired (this.id + " cancelOrder requires a symbol argument. To cancel the order, pass a symbol argument and {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument of cancelOrder.");
}
const price = this.safeFloat (params, 'price');
const qty = this.safeFloat (params, 'qty');
const isAsk = this.safeInteger (params, 'is_ask');
if ((price === undefined) || (qty === undefined) || (isAsk === undefined)) {
// eslint-disable-next-line quotes
throw new ArgumentsRequired (this.id + " cancelOrder requires {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument.");
}
await this.loadMarkets ();
const request = {
'order_id': id,
'price': price,
'qty': qty,
'is_ask': isAsk,
'currency': this.marketId (symbol),
};
const response = await this.privatePostOrderCancel (this.extend (request, params));
//
// {
// "result": "success",
// "errorCode": "0"
// }
//
return response;
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
const request = this.implodeParams (path, params);
const query = this.omit (params, this.extractParams (path));
let url = this.urls['api'] + '/';
if (api === 'public') {
url += request;
if (Object.keys (query).length) {
url += '?' + this.urlencode (query);
}
} else {
this.checkRequiredCredentials ();
url += this.version + '/' + request;
const nonce = this.nonce ().toString ();
const json = this.json (this.extend ({
'access_token': this.apiKey,
'nonce': nonce,
}, params));
const payload = this.stringToBase64 (json);
body = this.decode (payload);
const secret = this.secret.toUpperCase ();
const signature = this.hmac (payload, this.encode (secret), 'sha512');
headers = {
'Content-Type': 'application/json',
'X-COINONE-PAYLOAD': payload,
'X-COINONE-SIGNATURE': signature,
};
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (response === undefined) {
return;
}
if ('result' in response) {
const result = response['result'];
if (result !== 'success') {
//
// { "errorCode": "405", "status": "maintenance", "result": "error"}
//
const errorCode = this.safeString (response, 'errorCode');
const feedback = this.id + ' ' + body;
this.throwExactlyMatchedException (this.exceptions, errorCode, feedback);
throw new ExchangeError (feedback);
}
} else {
throw new ExchangeError (this.id + ' ' + body);
}
}
};
@@ -15,8 +15,14 @@ module.exports = class coinspot extends Exchange {
'countries': [ 'AU' ], // Australia
'rateLimit': 1000,
'has': {
'cancelOrder': false,
'CORS': false,
'createMarketOrder': false,
'createOrder': true,
'fetchBalance': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/28208429-3cacdf9a-6896-11e7-854e-4c79a772a30f.jpg',
@@ -48,6 +54,17 @@ module.exports = class coinspot extends Exchange {
'my/sell',
'my/buy/cancel',
'my/sell/cancel',
'ro/my/balances',
'ro/my/balances/{cointype}',
'ro/my/deposits',
'ro/my/withdrawals',
'ro/my/transactions',
'ro/my/transactions/{cointype}',
'ro/my/transactions/open',
'ro/my/transactions/{cointype}/open',
'ro/my/sendreceive',
'ro/my/affiliatepayments',
'ro/my/referralpayments',
],
},
},
@@ -127,10 +144,57 @@ module.exports = class coinspot extends Exchange {
'cointype': market['id'],
};
const response = await this.privatePostOrdersHistory (this.extend (request, params));
//
// {
// "status":"ok",
// "orders":[
// {"amount":0.00102091,"rate":21549.09999991,"total":21.99969168,"coin":"BTC","solddate":1604890646143,"market":"BTC/AUD"},
// ],
// }
//
const trades = this.safeValue (response, 'orders', []);
return this.parseTrades (trades, market, since, limit);
}
parseTrade (trade, market = undefined) {
//
// public fetchTrades
//
// {
// "amount":0.00102091,
// "rate":21549.09999991,
// "total":21.99969168,
// "coin":"BTC",
// "solddate":1604890646143,
// "market":"BTC/AUD"
// }
//
const price = this.safeFloat (trade, 'rate');
const amount = this.safeFloat (trade, 'amount');
let cost = this.safeFloat (trade, 'total');
if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) {
cost = price * amount;
}
const timestamp = this.safeInteger (trade, 'solddate');
const marketId = this.safeString (trade, 'market');
const symbol = this.safeSymbol (marketId, market, '/');
return {
'info': trade,
'id': undefined,
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'order': undefined,
'type': undefined,
'side': undefined,
'takerOrMaker': undefined,
'price': price,
'amount': amount,
'cost': cost,
'fee': undefined,
};
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
const method = 'privatePostMy' + this.capitalize (side);
@@ -18,21 +18,28 @@ module.exports = class crex24 extends Exchange {
// new metainfo interface
'has': {
'cancelAllOrders': true,
'cancelOrder': true,
'CORS': false,
'createOrder': true,
'editOrder': true,
'fetchBalance': true,
'fetchBidsAsks': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchFundingFees': false,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchOrderTrades': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchTradingFee': false, // actually, true, but will be implemented later
'fetchTradingFees': false, // actually, true, but will be implemented later
'fetchTransactions': true,
@@ -119,9 +126,15 @@ module.exports = class crex24 extends Exchange {
},
},
'commonCurrencies': {
'YOYO': 'YOYOW',
'BULL': 'BuySell',
'BCC': 'BCH',
'BIT': 'BitMoney',
'BULL': 'BuySell',
'CREDIT': 'TerraCredit',
'GHOST': 'GHOSTPRISM',
'IQ': 'IQ.Cash',
'PUT': 'PutinCoin',
'UNI': 'Universe',
'YOYO': 'YOYOW',
},
// exchange-specific options
'options': {
@@ -148,6 +161,7 @@ module.exports = class crex24 extends Exchange {
'API Key': AuthenticationError, // "API Key '9edc48de-d5b0-4248-8e7e-f59ffcd1c7f1' doesn't exist."
'Insufficient funds': InsufficientFunds, // "Insufficient funds: new order requires 10 ETH which is more than the available balance."
'has been delisted.': BadSymbol, // {"errorDescription":"Instrument '$PAC-BTC' has been delisted."}
'Mandatory parameter': BadRequest, // {"errorDescription":"Mandatory parameter 'feeCurrency' is missing."}
},
},
});
@@ -375,17 +389,8 @@ module.exports = class crex24 extends Exchange {
// timestamp: "2018-10-31T09:21:25Z" } ]
//
const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp'));
let symbol = undefined;
const marketId = this.safeString (ticker, 'instrument');
market = this.safeValue (this.markets_by_id, marketId, market);
if (market !== undefined) {
symbol = market['symbol'];
} else if (marketId !== undefined) {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
const symbol = this.safeSymbol (marketId, market, '-');
const last = this.safeFloat (ticker, 'last');
return {
'symbol': symbol,
@@ -519,12 +524,8 @@ module.exports = class crex24 extends Exchange {
const id = this.safeString (trade, 'id');
const side = this.safeString (trade, 'side');
const orderId = this.safeString (trade, 'orderId');
let symbol = undefined;
const marketId = this.safeString (trade, 'instrument');
market = this.safeValue (this.markets_by_id, marketId, market);
if (market !== undefined) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market, '-');
let fee = undefined;
const feeCurrencyId = this.safeString (trade, 'feeCurrency');
const feeCode = this.safeCurrencyCode (feeCurrencyId);
@@ -576,17 +577,19 @@ module.exports = class crex24 extends Exchange {
return this.parseTrades (response, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
// { timestamp: '2019-09-21T10:36:00Z',
// open: 0.02152,
// high: 0.02156,
// low: 0.02152,
// close: 0.02156,
// volume: 0.01741259 }
const date = this.safeString (ohlcv, 'timestamp');
const timestamp = this.parse8601 (date);
parseOHLCV (ohlcv, market = undefined) {
//
// {
// timestamp: '2019-09-21T10:36:00Z',
// open: 0.02152,
// high: 0.02156,
// low: 0.02152,
// close: 0.02156,
// volume: 0.01741259
// }
//
return [
timestamp,
this.parse8601 (this.safeString (ohlcv, 'timestamp')),
this.safeFloat (ohlcv, 'open'),
this.safeFloat (ohlcv, 'high'),
this.safeFloat (ohlcv, 'low'),
@@ -606,6 +609,18 @@ module.exports = class crex24 extends Exchange {
request['limit'] = limit; // Accepted values: 1 - 1000. If the parameter is not specified, the number of results is limited to 100
}
const response = await this.publicGetOhlcv (this.extend (request, params));
//
// [
// {
// "timestamp": "2020-06-06T17:36:00Z",
// "open": 0.025,
// "high": 0.025,
// "low": 0.02499,
// "close": 0.02499,
// "volume": 0.00643127
// }
// ]
//
return this.parseOHLCVs (response, market, timeframe, since, limit);
}
@@ -645,21 +660,8 @@ module.exports = class crex24 extends Exchange {
// }
//
const status = this.parseOrderStatus (this.safeString (order, 'status'));
let symbol = undefined;
const marketId = this.safeString (order, 'instrument');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
const [ baseId, quoteId ] = marketId.split ('-');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market, '-');
const timestamp = this.parse8601 (this.safeString (order, 'timestamp'));
let price = this.safeFloat (order, 'price');
const amount = this.safeFloat (order, 'volume');
@@ -702,6 +704,8 @@ module.exports = class crex24 extends Exchange {
cost = parseFloat (this.costToPrecision (symbol, cost));
}
}
const timeInForce = this.safeString (order, 'timeInForce');
const stopPrice = this.safeFloat (order, 'stopPrice');
return {
'info': order,
'id': id,
@@ -711,8 +715,10 @@ module.exports = class crex24 extends Exchange {
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'side': side,
'price': price,
'stopPrice': stopPrice,
'amount': amount,
'cost': cost,
'average': average,
@@ -762,6 +768,7 @@ module.exports = class crex24 extends Exchange {
} else {
request['stopPrice'] = this.priceToPrecision (symbol, stopPrice);
}
params = this.omit (params, 'stopPrice');
}
const response = await this.tradingPostPlaceOrder (this.extend (request, params));
//
@@ -1233,6 +1240,7 @@ module.exports = class crex24 extends Exchange {
// true - balance will be decreased by amount, whereas [amount - fee] will be transferred to the specified address
// false - amount will be deposited to the specified address, whereas the balance will be decreased by [amount + fee]
// 'includeFee': false, // the default value is false
'feeCurrency': currency['id'], // https://github.com/ccxt/ccxt/issues/7544
};
if (tag !== undefined) {
request['paymentId'] = tag;
@@ -1264,8 +1272,7 @@ module.exports = class crex24 extends Exchange {
body = this.json (params);
auth += body;
}
const signature = this.stringToBase64 (this.hmac (this.encode (auth), secret, 'sha512', 'binary'));
headers['X-CREX24-API-SIGN'] = this.decode (signature);
headers['X-CREX24-API-SIGN'] = this.hmac (this.encode (auth), secret, 'sha512', 'base64');
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
File diff suppressed because it is too large. Load diff
@@ -18,28 +18,32 @@ module.exports = class deribit extends Exchange {
'userAgent': undefined,
'rateLimit': 500,
'has': {
'cancelAllOrders': true,
'cancelOrder': true,
'CORS': true,
'createDepositAddress': true,
'createOrder': true,
'editOrder': true,
'fetchBalance': true,
'fetchOrder': true,
'fetchOrders': false,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchMyTrades': true,
'fetchTickers': true,
'fetchOHLCV': true,
'fetchDepositAddress': true,
'createDepositAddress': true,
'fetchOrderTrades': true,
'createOrder': true,
'cancelOrder': true,
'cancelAllOrders': true,
'withdraw': true,
'fetchTime': true,
'fetchStatus': true,
'fetchDeposits': true,
'fetchWithdrawals': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': false,
'fetchOrderTrades': true,
'fetchStatus': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchTransactions': false,
'fetchWithdrawals': true,
'withdraw': true,
},
'timeframes': {
'1m': '1',
@@ -649,13 +653,7 @@ module.exports = class deribit extends Exchange {
//
const timestamp = this.safeInteger2 (ticker, 'timestamp', 'creation_timestamp');
const marketId = this.safeString (ticker, 'instrument_name');
let symbol = marketId;
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market);
const last = this.safeFloat2 (ticker, 'last_price', 'last');
const stats = this.safeValue (ticker, 'stats', ticker);
return {
@@ -860,15 +858,8 @@ module.exports = class deribit extends Exchange {
// }
//
const id = this.safeString (trade, 'trade_id');
let symbol = undefined;
const marketId = this.safeString (trade, 'instrument_name');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
}
if ((symbol === undefined) && (market !== undefined)) {
symbol = market['symbol'];
}
const symbol = this.safeSymbol (marketId, market);
const timestamp = this.safeInteger (trade, 'timestamp');
const side = this.safeString (trade, 'direction');
const price = this.safeFloat (trade, 'price');
@@ -1025,6 +1016,15 @@ module.exports = class deribit extends Exchange {
return this.safeString (statuses, status, status);
}
parseTimeInForce (timeInForce) {
const timeInForces = {
'good_til_cancelled': 'GTC',
'fill_or_kill': 'FOK',
'immediate_or_cancel': 'IOC',
};
return this.safeString (timeInForces, timeInForce, timeInForce);
}
parseOrder (order, market = undefined) {
//
// createOrder
@@ -1078,21 +1078,7 @@ module.exports = class deribit extends Exchange {
}
const status = this.parseOrderStatus (this.safeString (order, 'order_state'));
const marketId = this.safeString (order, 'instrument_name');
let symbol = undefined;
let base = undefined;
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
base = market['base'];
}
if (market !== undefined) {
if (symbol === undefined) {
symbol = market['symbol'];
}
if (base === undefined) {
base = market['base'];
}
}
market = this.safeMarket (marketId, market);
const side = this.safeStringLower (order, 'direction');
let feeCost = this.safeFloat (order, 'commission');
let fee = undefined;
@@ -1100,7 +1086,7 @@ module.exports = class deribit extends Exchange {
feeCost = Math.abs (feeCost);
fee = {
'cost': feeCost,
'currency': base,
'currency': market['base'],
};
}
const type = this.safeString (order, 'order_type');
@@ -1109,6 +1095,8 @@ module.exports = class deribit extends Exchange {
if (trades !== undefined) {
trades = this.parseTrades (trades, market);
}
const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force'));
const stopPrice = undefined;
return {
'info': order,
'id': id,
@@ -1116,10 +1104,12 @@ module.exports = class deribit extends Exchange {
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'symbol': market['symbol'],
'type': type,
'timeInForce': timeInForce,
'side': side,
'price': price,
'stopPrice': stopPrice,
'amount': amount,
'cost': cost,
'average': average,
@@ -1211,6 +1201,7 @@ module.exports = class deribit extends Exchange {
} else {
request['stop_price'] = this.priceToPrecision (symbol, stopPrice);
}
params = this.omit (params, [ 'stop_price', 'stopPrice' ]);
}
const method = 'privateGet' + this.capitalize (side);
const response = await this[method] (this.extend (request, params));
@@ -1634,6 +1625,86 @@ module.exports = class deribit extends Exchange {
};
}
async fetchPosition (symbol, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'instrument_name': market['id'],
};
const response = await this.privateGetPosition (this.extend (request, params));
//
// {
// "jsonrpc": "2.0",
// "id": 404,
// "result": {
// "average_price": 0,
// "delta": 0,
// "direction": "buy",
// "estimated_liquidation_price": 0,
// "floating_profit_loss": 0,
// "index_price": 3555.86,
// "initial_margin": 0,
// "instrument_name": "BTC-PERPETUAL",
// "leverage": 100,
// "kind": "future",
// "maintenance_margin": 0,
// "mark_price": 3556.62,
// "open_orders_margin": 0.000165889,
// "realized_profit_loss": 0,
// "settlement_price": 3555.44,
// "size": 0,
// "size_currency": 0,
// "total_profit_loss": 0
// }
// }
//
// todo unify parsePosition/parsePositions
const result = this.safeValue (response, 'result');
return result;
}
async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const code = this.codeFromOptions ('fetchPositions');
const currency = this.currency (code);
const request = {
'currency': currency['id'],
};
const response = await this.privateGetPositions (this.extend (request, params));
//
// {
// "jsonrpc": "2.0",
// "id": 2236,
// "result": [
// {
// "average_price": 7440.18,
// "delta": 0.006687487,
// "direction": "buy",
// "estimated_liquidation_price": 1.74,
// "floating_profit_loss": 0,
// "index_price": 7466.79,
// "initial_margin": 0.000197283,
// "instrument_name": "BTC-PERPETUAL",
// "kind": "future",
// "leverage": 34,
// "maintenance_margin": 0.000143783,
// "mark_price": 7476.65,
// "open_orders_margin": 0.000197288,
// "realized_funding": -1e-8,
// "realized_profit_loss": -9e-9,
// "settlement_price": 7476.65,
// "size": 50,
// "size_currency": 0.006687487,
// "total_profit_loss": 0.000032781
// }
// ]
// }
//
// todo unify parsePosition/parsePositions
const result = this.safeValue (response, 'result', []);
return result;
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
this.checkAddress (address);
await this.loadMarkets ();
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { AccountSuspended, BadRequest, BadResponse, NetworkError, DDoSProtection, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, InsufficientFunds, InvalidOrder, InvalidNonce, OrderNotFound } = require ('./base/errors');
const { AccountSuspended, BadRequest, BadResponse, NetworkError, DDoSProtection, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, InsufficientFunds, InvalidOrder, InvalidNonce, OrderNotFound, InvalidAddress, RateLimitExceeded, BadSymbol } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -16,14 +16,28 @@ module.exports = class digifinex extends Exchange {
'version': 'v3',
'rateLimit': 900, // 300 for posts
'has': {
'cancelOrder': true,
'cancelOrders': true,
'fetchOrders': true,
'createOrder': true,
'fetchBalance': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchLedger': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchStatus': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchMyTrades': true,
'fetchLedger': true,
'fetchTime': true,
'fetchTrades': true,
'fetchWithdrawals': true,
'withdraw': true,
},
'timeframes': {
'1m': '1',
@@ -37,14 +51,14 @@ module.exports = class digifinex extends Exchange {
'1w': '1W',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/62184319-304e8880-b366-11e9-99fe-8011d6929195.jpg',
'api': 'https://openapi.digifinex.vip',
'www': 'https://www.digifinex.vip',
'logo': 'https://user-images.githubusercontent.com/51840849/87443315-01283a00-c5fe-11ea-8628-c2a0feaf07ac.jpg',
'api': 'https://openapi.digifinex.com',
'www': 'https://www.digifinex.com',
'doc': [
'https://docs.digifinex.vip',
'https://docs.digifinex.com',
],
'fees': 'https://digifinex.zendesk.com/hc/en-us/articles/360000328422-Fee-Structure-on-DigiFinex',
'referral': 'https://www.digifinex.vip/en-ww/from/DhOzBg/3798****5114',
'referral': 'https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp',
},
'api': {
'v2': {
@@ -58,12 +72,15 @@ module.exports = class digifinex extends Exchange {
'kline',
'margin/currencies',
'margin/symbols',
'markets', // undocumented
'markets',
'order_book',
'ping',
'spot/symbols',
'time',
'trades',
'trades/symbols',
'ticker',
'currencies', // todo add fetchCurrencies
],
},
'private': {
@@ -71,6 +88,7 @@ module.exports = class digifinex extends Exchange {
'{market}/financelog',
'{market}/mytrades',
'{market}/order',
'{market}​/order​/detail', // todo add fetchOrder
'{market}/order/current',
'{market}/order/history',
'margin/assets',
@@ -87,16 +105,22 @@ module.exports = class digifinex extends Exchange {
'spot/order',
'spot/order/current',
'spot/order/history',
'deposit/address', // todo add fetchDepositAddress
'deposit/history', // todo add fetchDeposits
'withdraw/history', // todo add fetchWithdrawals
],
'post': [
'{market}/order/cancel',
'{market}/order/new',
'{market}​/order​/batch_new',
'margin/order/cancel',
'margin/order/new',
'margin/position/close',
'spot/order/cancel',
'spot/order/new',
'transfer',
'withdraw/new', // todo add withdraw()
'withdraw/cancel',
],
},
},
@@ -136,6 +160,27 @@ module.exports = class digifinex extends Exchange {
'20019': [ BadRequest, 'Wrong trading pair symbol. Correct format:"usdt_btc". Quote asset is in the front' ],
'20020': [ DDoSProtection, "You have violated the API operation trading rules and temporarily forbid trading. At present, we have certain restrictions on the user's transaction rate and withdrawal rate." ],
'50000': [ ExchangeError, 'Exception error' ],
'20021': [ BadRequest, 'Invalid currency' ],
'20022': [ BadRequest, 'The ending timestamp must be larger than the starting timestamp' ],
'20023': [ BadRequest, 'Invalid transfer type' ],
'20024': [ BadRequest, 'Invalid amount' ],
'20025': [ BadRequest, 'This currency is not transferable at the moment' ],
'20026': [ InsufficientFunds, 'Transfer amount exceed your balance' ],
'20027': [ PermissionDenied, 'Abnormal account status' ],
'20028': [ PermissionDenied, 'Blacklist for transfer' ],
'20029': [ PermissionDenied, 'Transfer amount exceed your daily limit' ],
'20030': [ BadRequest, 'You have no position on this trading pair' ],
'20032': [ PermissionDenied, 'Withdrawal limited' ],
'20033': [ BadRequest, 'Wrong Withdrawal ID' ],
'20034': [ PermissionDenied, 'Withdrawal service of this crypto has been closed' ],
'20035': [ PermissionDenied, 'Withdrawal limit' ],
'20036': [ ExchangeError, 'Withdrawal cancellation failed' ],
'20037': [ InvalidAddress, 'The withdrawal address, Tag or chain type is not included in the withdrawal management list' ],
'20038': [ InvalidAddress, 'The withdrawal address is not on the white list' ],
'20039': [ ExchangeError, "Can't be canceled in current status" ],
'20040': [ RateLimitExceeded, 'Withdraw too frequently; limitation: 3 times a minute, 100 times a day' ],
'20041': [ PermissionDenied, 'Beyond the daily withdrawal limit' ],
'20042': [ BadSymbol, 'Current trading pair does not support API trading' ],
},
'broad': {
},
@@ -146,34 +191,130 @@ module.exports = class digifinex extends Exchange {
},
'commonCurrencies': {
'BHT': 'Black House Test',
'MBN': 'Mobilian Coin',
'TEL': 'TEL666',
},
});
}
async fetchMarketsByType (type, params = {}) {
const method = 'publicGet' + this.capitalize (type) + 'Symbols';
const response = await this[method] (params);
async fetchCurrencies (params = {}) {
const response = await this.publicGetCurrencies (params);
//
// {
// "symbol_list": [
// "data":[
// {
// "deposit_status":1,
// "min_deposit_amount":10,
// "withdraw_fee_rate":0,
// "min_withdraw_amount":10,
// "min_withdraw_fee":5,
// "currency":"USDT",
// "withdraw_status":0,
// "chain":"OMNI"
// },
// {
// "deposit_status":1,
// "min_deposit_amount":10,
// "withdraw_fee_rate":0,
// "min_withdraw_amount":10,
// "min_withdraw_fee":3,
// "currency":"USDT",
// "withdraw_status":1,
// "chain":"ERC20"
// },
// {
// "deposit_status":0,
// "min_deposit_amount":0,
// "withdraw_fee_rate":0,
// "min_withdraw_amount":0,
// "min_withdraw_fee":0,
// "currency":"DGF13",
// "withdraw_status":0,
// "chain":""
// },
// ],
// "code":200
// }
//
const data = this.safeValue (response, 'data', []);
const result = {};
for (let i = 0; i < data.length; i++) {
const currency = data[i];
const id = this.safeString (currency, 'currency');
const code = this.safeCurrencyCode (id);
const depositStatus = this.safeValue (currency, 'deposit_status', 1);
const withdrawStatus = this.safeValue (currency, 'withdraw_status', 1);
const active = depositStatus && withdrawStatus;
const fee = this.safeFloat (currency, 'withdraw_fee_rate');
if (code in result) {
if (Array.isArray (result[code]['info'])) {
result[code]['info'].push (currency);
} else {
result[code]['info'] = [ result[code]['info'], currency ];
}
} else {
result[code] = {
'id': id,
'code': code,
'info': currency,
'type': undefined,
'name': undefined,
'active': active,
'fee': fee,
'precision': 8, // todo fix hardcoded value
'limits': {
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': undefined,
'max': undefined,
},
'withdraw': {
'min': this.safeFloat (currency, 'min_withdraw_amount'),
'max': undefined,
},
},
};
}
}
return result;
}
async fetchMarkets (params = {}) {
const options = this.safeValue (this.options, 'fetchMarkets', {});
const method = this.safeString (options, 'method', 'fetch_markets_v2');
return await this[method] (params);
}
async fetchMarketsV2 (params = {}) {
const response = await this.publicGetTradesSymbols (params);
//
// {
// "symbol_list":[
// {
// "order_types":["LIMIT","MARKET"],
// "quote_asset":"USDT",
// "minimum_value":2,
// "amount_precision":4,
// "status":"TRADING",
// "minimum_amount":0.001,
// "symbol":"LTC_USDT",
// "margin_rate":0.3,
// "minimum_amount":0.0001,
// "symbol":"BTC_USDT",
// "is_allow":1,
// "zone":"MAIN",
// "base_asset":"LTC",
// "base_asset":"BTC",
// "price_precision":2
// },
// }
// ],
// "code":0
// }
//
const markets = this.safeValue (response, 'symbols_list', []);
const markets = this.safeValue (response, 'symbol_list', []);
const result = [];
for (let i = 0; i < markets.length; i++) {
const market = markets[i];
@@ -211,7 +352,9 @@ module.exports = class digifinex extends Exchange {
// const status = this.safeString (market, 'status');
// const active = (status === 'TRADING');
//
const active = undefined;
const isAllowed = this.safeValue (market, 'is_allow', 1);
const active = isAllowed ? true : false;
const type = 'spot';
const spot = (type === 'spot');
const margin = (type === 'margin');
result.push ({
@@ -233,7 +376,7 @@ module.exports = class digifinex extends Exchange {
return result;
}
async fetchMarkets (params = {}) {
async fetchMarketsV1 (params = {}) {
const response = await this.publicGetMarkets (params);
//
// {
@@ -394,20 +537,12 @@ module.exports = class digifinex extends Exchange {
'date': date,
}, tickers[reversedMarketId]);
const [ quoteId, baseId ] = reversedMarketId.split ('_');
const marketId = baseId + '_' + quoteId;
let market = undefined;
let symbol = undefined;
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
const marketId = baseId.toUpperCase () + '_' + quoteId.toUpperCase ();
const market = this.safeMarket (marketId, undefined, '_');
const symbol = market['symbol'];
result[symbol] = this.parseTicker (ticker, market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -418,7 +553,7 @@ module.exports = class digifinex extends Exchange {
await this.loadMarkets ();
const market = this.market (symbol);
// reversed base/quote in v2
const marketId = market['quoteId'] + '_' + market['baseId'];
const marketId = market['quoteId'].toLowerCase () + '_' + market['baseId'].toLowerCase ();
const request = {
'symbol': marketId,
'apiKey': apiKey,
@@ -535,24 +670,8 @@ module.exports = class digifinex extends Exchange {
cost = price * amount;
}
}
let symbol = undefined;
const marketId = this.safeString (trade, 'symbol');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[market];
symbol = market['symbol'];
} else {
const [ baseId, quoteId ] = marketId.split ('_');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
if (symbol === undefined) {
if (market !== undefined) {
symbol = market['symbol'];
}
}
const symbol = this.safeSymbol (marketId, market, '_');
const takerOrMaker = this.safeValue (trade, 'is_maker');
const feeCost = this.safeFloat (trade, 'fee');
let fee = undefined;
@@ -581,6 +700,32 @@ module.exports = class digifinex extends Exchange {
};
}
async fetchTime (params = {}) {
const response = await this.publicGetTime (params);
//
// {
// "server_time": 1589873762,
// "code": 0
// }
//
return this.safeTimestamp (response, 'server_time');
}
async fetchStatus (params = {}) {
await this.publicGetPing (params);
//
// {
// "msg": "pong",
// "code": 0
// }
//
this.status = this.extend (this.status, {
'status': 'ok',
'updated': this.milliseconds (),
});
return this.status;
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
@@ -617,14 +762,24 @@ module.exports = class digifinex extends Exchange {
return this.parseTrades (data, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// [
// 1556712900,
// 2205.899,
// 0.029967,
// 0.02997,
// 0.029871,
// 0.029927
// ]
//
return [
ohlcv[0] * 1000, // timestamp
ohlcv[5], // open
ohlcv[3], // high
ohlcv[4], // low
ohlcv[2], // close
ohlcv[1], // volume
this.safeTimestamp (ohlcv, 0),
this.safeFloat (ohlcv, 5), // open
this.safeFloat (ohlcv, 3), // high
this.safeFloat (ohlcv, 4), // low
this.safeFloat (ohlcv, 2), // close
this.safeFloat (ohlcv, 1), // volume
];
}
@@ -813,25 +968,8 @@ module.exports = class digifinex extends Exchange {
}
}
const status = this.parseOrderStatus (this.safeString (order, 'status'));
if (market === undefined) {
const exchange = order['symbol'].toUpperCase ();
if (exchange in this.markets_by_id) {
market = this.markets_by_id[exchange];
}
}
let symbol = undefined;
const marketId = this.safeString (order, 'symbol');
if (marketId !== undefined) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
symbol = market['symbol'];
} else {
const [ baseId, quoteId ] = marketId.split ('_');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
}
}
const symbol = this.safeSymbol (marketId, market, '_');
const amount = this.safeFloat (order, 'amount');
const filled = this.safeFloat (order, 'executed_amount');
const price = this.safeFloat (order, 'price');
@@ -855,8 +993,10 @@ module.exports = class digifinex extends Exchange {
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'filled': filled,
'remaining': remaining,
@@ -987,8 +1127,12 @@ module.exports = class digifinex extends Exchange {
// ]
// }
//
const data = this.safeValue (response, 'data', {});
return this.parseOrder (data, market);
const data = this.safeValue (response, 'data', []);
const order = this.safeValue (data, 0);
if (order === undefined) {
throw new OrderNotFound (this.id + ' fetchOrder() order ' + id + ' not found');
}
return this.parseOrder (order, market);
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -1118,6 +1262,218 @@ module.exports = class digifinex extends Exchange {
return this.parseLedger (items, currency, since, limit);
}
parseDepositAddresses (addresses) {
const result = {};
for (let i = 0; i < addresses.length; i++) {
const address = this.parseDepositAddress (addresses[i]);
const code = address['currency'];
result[code] = address;
}
return result;
}
parseDepositAddress (depositAddress, currency = undefined) {
//
// {
// "addressTag":"",
// "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373",
// "currency":"USDT",
// "chain":"ERC20"
// }
//
const address = this.safeString (depositAddress, 'address');
const tag = this.safeString (depositAddress, 'addressTag');
const currencyId = this.safeStringUpper (depositAddress, 'currency');
const code = this.safeCurrencCode (currencyId);
return {
'info': depositAddress,
'code': code,
'address': address,
'tag': tag,
};
}
async fetchDepositAddress (code, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
'currency': currency['id'],
};
const response = await this.privateGetDepositAddress (this.extend (request, params));
//
// {
// "data":[
// {
// "addressTag":"",
// "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373",
// "currency":"USDT",
// "chain":"ERC20"
// }
// ],
// "code":200
// }
//
const data = this.safeValue (response, 'data', []);
const addresses = this.parseDepositAddresses (data);
const address = this.safeValue (addresses, code);
if (address === undefined) {
throw new InvalidAddress (this.id + ' fetchDepositAddress did not return an address for ' + code + ' - create the deposit address in the user settings on the exchange website first.');
}
return address;
}
async fetchTransactionsByType (type, code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
let currency = undefined;
const request = {
// 'currency': currency['id'],
// 'from': 'fromId', // When direct is' prev ', from is 1, returning from old to new ascending, when direct is' next ', from is the ID of the most recent record, returned from the old descending order
// 'size': 100, // default 100, max 500
// 'direct': 'prev', // "prev" ascending, "next" descending
};
if (code !== undefined) {
currency = this.currency (code);
request['currency'] = currency['id'];
}
if (limit !== undefined) {
request['size'] = Math.min (500, limit);
}
const method = (type === 'deposit') ? 'privateGetDepositHistory' : 'privateGetWithdrawHistory';
const response = await this[method] (this.extend (request, params));
//
// {
// "code": 200,
// "data": [
// {
// "id": 1171,
// "currency": "xrp",
// "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b",
// "chain": "",
// "amount": 7.457467,
// "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm",
// "memo": "100040",
// "fee": 0,
// "state": "safe",
// "created_date": "2020-04-20 11:23:00",
// "finished_date": "2020-04-20 13:23:00"
// },
// ]
// }
//
const data = this.safeValue (response, 'data', []);
return this.parseTransactions (data, currency, since, limit, { 'type': type });
}
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
return await this.fetchTransactionsByType ('deposit', code, since, limit, params);
}
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
return await this.fetchTransactionsByType ('withdrawal', code, since, limit, params);
}
parseTransactionStatus (status) {
const statuses = {
'0': 'pending', // Email Sent
'1': 'canceled', // Cancelled (different from 1 = ok in deposits)
'2': 'pending', // Awaiting Approval
'3': 'failed', // Rejected
'4': 'pending', // Processing
'5': 'failed', // Failure
'6': 'ok', // Completed
};
return this.safeString (statuses, status, status);
}
parseTransaction (transaction, currency = undefined) {
//
// withdraw
//
// {
// "code": 200,
// "withdraw_id": 700
// }
//
// fetchDeposits, fetchWithdrawals
//
// {
// "id": 1171,
// "currency": "xrp",
// "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b",
// "chain": "",
// "amount": 7.457467,
// "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm",
// "memo": "100040",
// "fee": 0,
// "state": "safe",
// "created_date": "2020-04-20 11:23:00",
// "finished_date": "2020-04-20 13:23:00"
// }
//
const id = this.safeString2 (transaction, 'id', 'withdraw_id');
const address = this.safeString (transaction, 'address');
let tag = this.safeString (transaction, 'memo'); // set but unused
if (tag !== undefined) {
if (tag.length < 1) {
tag = undefined;
}
}
const txid = this.safeString (transaction, 'hash');
const currencyId = this.safeStringUpper (transaction, 'currency');
const code = this.safeCurrencyCode (currencyId, currency);
const timestamp = this.parse8601 (this.safeString (transaction, 'created_date'));
const updated = this.parse8601 (this.safeString (transaction, 'finished_date'));
const status = this.parseTransactionStatus (this.safeString (transaction, 'state'));
const amount = this.safeFloat (transaction, 'amount');
const feeCost = this.safeFloat (transaction, 'fee');
let fee = undefined;
if (feeCost !== undefined) {
fee = { 'currency': code, 'cost': feeCost };
}
return {
'info': transaction,
'id': id,
'txid': txid,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'address': address,
'addressTo': address,
'addressFrom': undefined,
'tag': tag,
'tagTo': tag,
'tagFrom': undefined,
'type': undefined,
'amount': amount,
'currency': code,
'status': status,
'updated': updated,
'fee': fee,
};
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
this.checkAddress (address);
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
// 'chain': 'ERC20', 'OMNI', 'TRC20', // required for USDT
'address': address,
'amount': parseFloat (amount),
'currency': currency['id'],
};
if (tag !== undefined) {
request['memo'] = tag;
}
const response = await this.privatePostWithdrawNew (this.extend (request, params));
//
// {
// "code": 200,
// "withdraw_id": 700
// }
//
return this.parseTransaction (response, currency);
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
const version = (api === 'v2') ? api : this.version;
let url = this.urls['api'] + '/' + version + '/' + this.implodeParams (path, params);
@@ -1153,17 +1509,12 @@ module.exports = class digifinex extends Exchange {
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
dateUTC8 (timestampMS) {
const timedelta = this.safeValue (this.options, 'timedelta', 8 * 60 * 60 * 1000); // eight hours
return this.ymd (timestampMS + timedelta);
}
handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) {
if (!response) {
return; // fall back to default error handler
}
const code = this.safeString (response, 'code');
if (code === '0') {
if ((code === '0') || (code === '200')) {
return; // no error
}
const feedback = this.id + ' ' + responseBody;
@@ -16,31 +16,38 @@ module.exports = class dsx extends Exchange {
'rateLimit': 1500,
'version': 'v3',
'has': {
'cancelOrder': true,
'CORS': false,
'createMarketOrder': false,
'fetchOHLCV': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOpenOrders': true,
'fetchClosedOrders': false,
'fetchOrderBooks': false,
'createDepositAddress': true,
'createMarketOrder': false,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': false,
'fetchDepositAddress': true,
'fetchTransactions': true,
'fetchTickers': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrderBooks': true,
'fetchOrders': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTransactions': true,
'fetchTrades': true,
'withdraw': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/51840849/76909626-cb2bb100-68bc-11ea-99e0-28ba54f04792.jpg',
'api': {
'public': 'https://dsx.uk/mapi', // market data
'private': 'https://dsx.uk/tapi', // trading
'dwapi': 'https://dsx.uk/dwapi', // deposit/withdraw
'public': 'https://dsxglobal.com/mapi', // market data
'private': 'https://dsxglobal.com/tapi', // trading
'dwapi': 'https://dsxglobal.com/dwapi', // deposit/withdraw
},
'www': 'https://dsx.uk',
'www': 'https://dsxglobal.com',
'doc': [
'https://dsx.uk/developers/publicApi',
'https://dsxglobal.com/developers/publicApi',
],
},
'fees': {
@@ -117,6 +124,7 @@ module.exports = class dsx extends Exchange {
'data unavailable': ExchangeNotAvailable,
'external service unavailable': ExchangeNotAvailable,
'nonce is invalid': InvalidNonce, // {"success":0,"error":"Parameter: nonce is invalid"}
'Incorrect volume': InvalidOrder, // {"success": 0,"error":"Order was rejected. Incorrect volume."}
},
},
'options': {
@@ -539,7 +547,7 @@ module.exports = class dsx extends Exchange {
}
result[symbol] = this.parseTicker (ticker, market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -566,7 +574,7 @@ module.exports = class dsx extends Exchange {
return this.parseTrades (response[market['id']], market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "high" : 0.01955,
@@ -714,9 +722,6 @@ module.exports = class dsx extends Exchange {
'filled': filled,
'fee': undefined,
// 'trades': this.parseTrades (order['trades'], market),
'clientOrderId': undefined,
'average': undefined,
'trades': undefined,
};
}
@@ -834,8 +839,10 @@ module.exports = class dsx extends Exchange {
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'type': orderType,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'remaining': remaining,
@@ -843,7 +850,6 @@ module.exports = class dsx extends Exchange {
'status': status,
'fee': fee,
'trades': trades,
'average': undefined,
};
}
@@ -965,9 +971,6 @@ module.exports = class dsx extends Exchange {
'orderId': id,
};
const response = await this.privatePostOrderCancel (this.extend (request, params));
if (id in this.orders) {
this.orders[id]['status'] = 'canceled';
}
return response;
}
@@ -1188,7 +1191,7 @@ module.exports = class dsx extends Exchange {
body = this.urlencode (this.extend ({
'nonce': nonce,
}, query));
const signature = this.decode (this.hmac (this.encode (body), this.encode (this.secret), 'sha512', 'base64'));
const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512', 'base64');
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Key': this.apiKey,
File diff suppressed because it is too large. Load diff
@@ -16,23 +16,28 @@ module.exports = class exmo extends Exchange {
'rateLimit': 350, // once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second
'version': 'v1.1',
'has': {
'cancelOrder': true,
'CORS': false,
'fetchClosedOrders': 'emulated',
'createOrder': true,
'fetchBalance': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchFundingFees': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': 'emulated',
'fetchOrders': 'emulated',
'fetchOrderTrades': true,
'fetchOrderBook': true,
'fetchOrderBooks': true,
'fetchMyTrades': true,
'fetchOrderTrades': true,
'fetchTicker': true,
'fetchTickers': true,
'withdraw': true,
'fetchTrades': true,
'fetchTradingFee': true,
'fetchTradingFees': true,
'fetchFundingFees': true,
'fetchCurrencies': true,
'fetchTransactions': true,
'fetchOHLCV': true,
'withdraw': true,
},
'timeframes': {
'1m': '1',
@@ -73,11 +78,14 @@ module.exports = class exmo extends Exchange {
'public': {
'get': [
'currency',
'currency/list/extended',
'order_book',
'pair_settings',
'ticker',
'trades',
'candles_history',
'required_amount',
'payments/providers/crypto/list',
],
},
'private': {
@@ -91,13 +99,14 @@ module.exports = class exmo extends Exchange {
'user_trades',
'user_cancelled_orders',
'order_trades',
'required_amount',
'deposit_address',
'withdraw_crypt',
'withdraw_get_txid',
'excode_create',
'excode_load',
'code_check',
'wallet_history',
'wallet_operations',
],
},
},
@@ -121,229 +130,210 @@ module.exports = class exmo extends Exchange {
'error': '',
'data': {
'limits': [
{ 'pair': 'ALGO/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000' },
{ 'pair': 'ALGO/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '50' },
{ 'pair': 'ATOM/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '50000000' },
{ 'pair': 'ATOM/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'BTC/USD', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'BTC/RUB', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '2000000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'BTC/EUR', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'BTC/UAH', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '15000000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'BTC/GBP', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'ETH/GBP', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'XRP/GBP', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'USDT/GBP', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'BTC/PLN', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '20000000', 'min_a': '50', 'max_a': '2000000' },
{ 'pair': 'BTC/TRY', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '800000', 'min_a': '40', 'max_a': '6000000' },
{ 'pair': 'BTC/KZT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1000', 'max_p': '12000000', 'min_a': '1000', 'max_a': '100000000' },
{ 'pair': 'BTC/USDT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.01', 'max_p': '30000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETH/BTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ETH/USD', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETH/RUB', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000' },
{ 'pair': 'XRP/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'XRP/USD', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'XRP/RUB', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '50000000' },
{ 'pair': 'ADA/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ADA/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'ADA/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'ALGO/USDT', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'ALGO/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000' },
{ 'pair': 'ALGO/EUR', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'ATOM/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'ATOM/EUR', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'BCH/USD', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '30000', 'min_a': '0.0001', 'max_a': '500000' },
{ 'pair': 'BCH/RUB', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '2000000', 'min_a': '0.0001', 'max_a': '50000000' },
{ 'pair': 'BCH/EUR', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'BCH/UAH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.1', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'BCH/BTC', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '5', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'BCH/ETH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.0000001', 'max_p': '200', 'min_a': '0.0001', 'max_a': '5000' },
{ 'pair': 'BCH/USDT', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'BTCZ/BTC', 'min_q': '100', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'BTG/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'BTG/BTC', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'BTG/ETH', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'BTT/RUB', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'BTT/UAH', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'BTT/BTC', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'CRON/BTC', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'CRON/ETH', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000' },
{ 'pair': 'CRON/USDT', 'min_q': '1', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'CRON/EXM', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'DAI/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'DAI/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '0.5', 'max_a': '30000000' },
{ 'pair': 'DAI/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'DAI/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '5000' },
{ 'pair': 'DASH/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'DASH/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000' },
{ 'pair': 'DASH/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '200000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'DASH/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'DASH/USDT', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'DCR/RUB', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '3000000' },
{ 'pair': 'DCR/UAH', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.25', 'max_a': '1000000' },
{ 'pair': 'DCR/BTC', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'DOGE/USD', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'DOGE/BTC', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'DXT/USD', 'min_q': '100', 'max_q': '50000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'DXT/BTC', 'min_q': '100', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'EOS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'EOS/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'EOS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ETC/USD', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'ETC/RUB', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '50000000' },
{ 'pair': 'ETC/BTC', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '0.5', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ETH/EUR', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETH/UAH', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '1000000', 'min_a': '90', 'max_a': '15000000' },
{ 'pair': 'ETH/PLN', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '2000000' },
{ 'pair': 'ETH/TRY', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.1', 'max_p': '80000', 'min_a': '10', 'max_a': '6000000' },
{ 'pair': 'ETH/KZT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '4', 'max_p': '40000000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETH/USDT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETH/LTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '100000', 'min_a': '0.05', 'max_a': '100000' },
{ 'pair': 'ETZ/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '10' },
{ 'pair': 'ETZ/ETH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ETZ/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '1000' },
{ 'pair': 'EXM/USDT', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'EXM/ETH', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '5000' },
{ 'pair': 'GAS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'GAS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'GNT/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'GNT/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'GUSD/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'GUSD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'GUSD/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100' },
{ 'pair': 'HP/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'HB/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'INK/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'INK/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'INK/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'KICK/RUB', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'KICK/USDT', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'KICK/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'KICK/ETH', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '5000' },
{ 'pair': 'LSK/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'LSK/RUB', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '50000000' },
{ 'pair': 'LSK/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100' },
{ 'pair': 'LTC/USD', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'LTC/RUB', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000' },
{ 'pair': 'LTC/EUR', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'LTC/UAH', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '5', 'max_a': '18000000' },
{ 'pair': 'LTC/BTC', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'MKR/BTC', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'MKR/DAI', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.5', 'max_p': '500000', 'min_a': '0.005', 'max_a': '500000' },
{ 'pair': 'MNC/USD', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'MNC/BTC', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'MNC/ETH', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '1000' },
{ 'pair': 'MNX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'MNX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'MNX/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'NEO/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'NEO/RUB', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1500000', 'min_a': '50', 'max_a': '50000000' },
{ 'pair': 'NEO/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'OMG/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'OMG/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'OMG/ETH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'ONG/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '15000000' },
{ 'pair': 'ONG/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10' },
{ 'pair': 'ONG/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '250000' },
{ 'pair': 'ONG/UAH', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '6000000' },
{ 'pair': 'ONT/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '15000000' },
{ 'pair': 'ONT/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10' },
{ 'pair': 'ONT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '6000000' },
{ 'pair': 'ONT/UAH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '250000' },
{ 'pair': 'PTI/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '600000', 'min_a': '10', 'max_a': '600000' },
{ 'pair': 'PTI/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '10' },
{ 'pair': 'PTI/EOS', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0000001', 'max_p': '5000', 'min_a': '0.01', 'max_a': '20000' },
{ 'pair': 'PTI/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'QTUM/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'QTUM/BTC', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'QTUM/ETH', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'ROOBEE/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'SMART/USD', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'SMART/RUB', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.0001', 'max_p': '100000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'SMART/EUR', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'SMART/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'TRX/USD', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'TRX/EUR', 'min_q': '0.01', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'TRX/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'TRX/UAH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'TRX/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'USDC/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'USDC/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'USDC/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '1000' },
{ 'pair': 'USDC/USDT', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'USDT/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'USDT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'USDT/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'USDT/UAH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '1', 'max_p': '3000', 'min_a': '2', 'max_a': '15000000' },
{ 'pair': 'USDT/KZT', 'min_q': '1', 'max_q': '500000', 'min_p': '200', 'max_p': '4000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'VLX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'WAVES/USD', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.001', 'max_p': '3500', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'WAVES/RUB', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000' },
{ 'pair': 'WAVES/BTC', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'WAVES/ETH', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.00001', 'max_p': '30', 'min_a': '0.0035', 'max_a': '3500' },
{ 'pair': 'XEM/USD', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'XEM/EUR', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'XEM/UAH', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'XEM/BTC', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00015', 'max_a': '100' },
{ 'pair': 'XLM/USD', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'XLM/RUB', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'XLM/TRY', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '6000000' },
{ 'pair': 'XLM/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'XMR/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'XMR/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '600000', 'min_a': '10', 'max_a': '16000000' },
{ 'pair': 'XMR/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'XMR/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '300000', 'min_a': '5', 'max_a': '16000000' },
{ 'pair': 'XMR/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'XMR/ETH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'XRP/EUR', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'XRP/TRY', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '6000000' },
{ 'pair': 'XRP/UAH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '15000000' },
{ 'pair': 'XRP/USDT', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'XRP/ETH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000' },
{ 'pair': 'XTZ/USD', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '100000' },
{ 'pair': 'XTZ/RUB', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'XTZ/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10' },
{ 'pair': 'XTZ/ETH', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '1000' },
{ 'pair': 'ZEC/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'ZEC/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'ZEC/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'ZEC/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ZRX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'ZRX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ZRX/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'ZAG/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'USD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'EXM/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0000001', 'max_a': '1' },
{ 'pair': 'ATMCASH/BTC', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '10' },
{ 'pair': 'BTC/USD', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/RUB', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '2000000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/EUR', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/GBP', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/UAH', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '15000000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/PLN', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '20000000', 'min_a': '50', 'max_a': '2000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/TRY', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '800000', 'min_a': '40', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/KZT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1000', 'max_p': '12000000', 'min_a': '1000', 'max_a': '100000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTC/USDT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.01', 'max_p': '30000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'ETH/BTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/USD', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/RUB', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/USD', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/RUB', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ADA/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ADA/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ADA/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ALGO/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ALGO/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '50', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ALGO/USDT', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'ALGO/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ALGO/EUR', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ATOM/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ATOM/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ATOM/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ATOM/EUR', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BCH/USD', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '30000', 'min_a': '0.0001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BCH/RUB', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '2000000', 'min_a': '0.0001', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BCH/EUR', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BCH/UAH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.1', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BCH/BTC', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '5', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BCH/ETH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.0000001', 'max_p': '200', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BCH/USDT', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'BTG/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTG/BTC', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTG/ETH', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTT/RUB', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTT/UAH', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'BTT/BTC', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'CRON/BTC', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'CRON/ETH', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'CRON/USDT', 'min_q': '1', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'CRON/EXM', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DAI/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DAI/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '0.5', 'max_a': '30000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DAI/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DAI/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DASH/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DASH/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DASH/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '200000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DASH/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DASH/USDT', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'DCR/RUB', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '3000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DCR/UAH', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.25', 'max_a': '1000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DCR/BTC', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DOGE/USD', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'DOGE/BTC', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'EOS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'EOS/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'EOS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETC/USD', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETC/RUB', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETC/BTC', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '0.5', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/EUR', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/GBP', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/UAH', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '1000000', 'min_a': '90', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/PLN', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '2000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/TRY', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.1', 'max_p': '80000', 'min_a': '10', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/KZT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '4', 'max_p': '40000000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETH/USDT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'ETH/LTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '100000', 'min_a': '0.05', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETZ/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETZ/ETH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ETZ/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '1000', 'taker': '0', 'maker': '0' },
{ 'pair': 'EXM/USDT', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0', 'maker': '0' },
{ 'pair': 'EXM/ETH', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'GAS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'GAS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'GNT/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'GNT/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'GUSD/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'GUSD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'GUSD/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'HP/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'HB/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LSK/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LSK/RUB', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LSK/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LTC/USD', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LTC/RUB', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LTC/EUR', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LTC/UAH', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '5', 'max_a': '18000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'LTC/BTC', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'MKR/BTC', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'MKR/DAI', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.5', 'max_p': '500000', 'min_a': '0.005', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'MNC/USD', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'MNC/BTC', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'MNC/ETH', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'NEO/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'NEO/RUB', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1500000', 'min_a': '50', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'NEO/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'OMG/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'OMG/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'OMG/ETH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONG/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONG/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONG/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '250000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONG/UAH', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONT/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONT/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ONT/UAH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '250000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'PTI/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '600000', 'min_a': '10', 'max_a': '600000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'PTI/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'PTI/EOS', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0000001', 'max_p': '5000', 'min_a': '0.01', 'max_a': '20000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'PTI/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0', 'maker': '0' },
{ 'pair': 'QTUM/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'QTUM/BTC', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'QTUM/ETH', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ROOBEE/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'SMART/USD', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'SMART/RUB', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.0001', 'max_p': '100000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'SMART/EUR', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'SMART/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'TRX/USD', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'TRX/EUR', 'min_q': '0.01', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'TRX/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'TRX/UAH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'TRX/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDC/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDC/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDC/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDC/USDT', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'USDT/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDT/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDT/GBP', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDT/UAH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '1', 'max_p': '3000', 'min_a': '2', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USDT/KZT', 'min_q': '1', 'max_q': '500000', 'min_p': '200', 'max_p': '4000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'VLX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'WAVES/USD', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.001', 'max_p': '3500', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'WAVES/RUB', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'WAVES/BTC', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'WAVES/ETH', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.00001', 'max_p': '30', 'min_a': '0.0035', 'max_a': '3500', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XEM/USD', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XEM/EUR', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XEM/UAH', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XEM/BTC', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XLM/USD', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XLM/RUB', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XLM/TRY', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XLM/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XMR/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XMR/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '600000', 'min_a': '10', 'max_a': '16000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XMR/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XMR/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '300000', 'min_a': '5', 'max_a': '16000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XMR/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XMR/ETH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/EUR', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/GBP', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/TRY', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/UAH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XRP/USDT', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0', 'maker': '0' },
{ 'pair': 'XRP/ETH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XTZ/USD', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XTZ/RUB', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XTZ/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'XTZ/ETH', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZEC/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZEC/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZEC/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZEC/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZRX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZRX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZRX/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'ZAG/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'USD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' },
{ 'pair': 'EXM/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0000001', 'max_a': '1', 'taker': '0.4', 'maker': '0.4' },
],
'fees': [
{
'group': 'crypto',
'title': 'Криптовалюта',
'title': 'Cryptocurrency',
'items': [
{ 'prov': 'EXM', 'dep': '0%', 'wd': '1 EXM' },
{ 'prov': 'BTC', 'dep': '0%', 'wd': '0.0005 BTC' },
{ 'prov': 'BTC', 'dep': '0%', 'wd': '0.0004 BTC' },
{ 'prov': 'LTC', 'dep': '0%', 'wd': '0.01 LTC' },
{ 'prov': 'DOGE', 'dep': '0%', 'wd': '1 DOGE' },
{ 'prov': 'DASH', 'dep': '0%', 'wd': '0.01 DASH' },
{ 'prov': 'ETH', 'dep': '0%', 'wd': '0.01 ETH' },
{ 'prov': 'DOGE', 'dep': '0%', 'wd': '1 Doge' },
{ 'prov': 'DASH', 'dep': '0%', 'wd': '0.002 DASH' },
{ 'prov': 'ETH', 'dep': '0%', 'wd': '0.003 ETH' },
{ 'prov': 'WAVES', 'dep': '0%', 'wd': '0.001 WAVES' },
{ 'prov': 'ZEC', 'dep': '0%', 'wd': '0.001 ZEC' },
{ 'prov': 'USDT', 'dep': '0%', 'wd': '5 USDT' },
{ 'prov': 'XMR', 'dep': '0%', 'wd': '0.05 XMR' },
{ 'prov': 'USDT', 'dep': '0%', 'wd': '' },
{ 'prov': 'XMR', 'dep': '0%', 'wd': '0.001 XMR' },
{ 'prov': 'XRP', 'dep': '0%', 'wd': '0.02 XRP' },
{ 'prov': 'KICK', 'dep': '0%', 'wd': '50 KICK' },
{ 'prov': 'ETC', 'dep': '0%', 'wd': '0.01 ETC' },
{ 'prov': 'BCH', 'dep': '0%', 'wd': '0.001 BCH' },
{ 'prov': 'BTG', 'dep': '0%', 'wd': '0.001 BTG' },
{ 'prov': 'EOS', 'dep': '0%', 'wd': '0.05 EOS' },
{ 'prov': 'BTCZ', 'dep': '0%', 'wd': '5 BTCZ' },
{ 'prov': 'DXT', 'dep': '20 DXT', 'wd': '20 DXT' },
{ 'prov': 'XLM', 'dep': '0%', 'wd': '0.01 XLM' },
{ 'prov': 'MNX', 'dep': '0%', 'wd': '0.01 MNX' },
{ 'prov': 'OMG', 'dep': '0.1 OMG', 'wd': '0.5 OMG' },
{ 'prov': 'TRX', 'dep': '0%', 'wd': '1 TRX' },
{ 'prov': 'ADA', 'dep': '0%', 'wd': '1 ADA' },
{ 'prov': 'INK', 'dep': '10 INK', 'wd': '50 INK' },
{ 'prov': 'NEO', 'dep': '0%', 'wd': '0%' },
{ 'prov': 'GAS', 'dep': '0%', 'wd': '0%' },
{ 'prov': 'ZRX', 'dep': '0%', 'wd': '1 ZRX' },
@@ -357,29 +347,28 @@ module.exports = class exmo extends Exchange {
{ 'prov': 'DAI', 'dep': '0%', 'wd': '1 DAI' },
{ 'prov': 'MKR', 'dep': '0%', 'wd': '0.005 MKR' },
{ 'prov': 'MNC', 'dep': '0%', 'wd': '15 MNC' },
{ 'prov': 'PTI', 'dep': '0%', 'wd': '10 PTI' },
{ 'prov': 'ATMCASH', 'dep': '0%', 'wd': '5 ATMCASH' },
{ 'prov': 'PTI', 'dep': '-', 'wd': '10 PTI' },
{ 'prov': 'ETZ', 'dep': '0%', 'wd': '1 ETZ' },
{ 'prov': 'USDC', 'dep': '0%', 'wd': '0.5 USDC' },
{ 'prov': 'ROOBEE', 'dep': '0%', 'wd': '200 ROOBEE' },
{ 'prov': 'DCR', 'dep': '0%', 'wd': '0.01 DCR' },
{ 'prov': 'ZAG', 'dep': '0%', 'wd': '0%' },
{ 'prov': 'BTT', 'dep': '0%', 'wd': '100 BTT' },
{ 'prov': 'BTT', 'dep': '0 BTT', 'wd': '100 BTT' },
{ 'prov': 'VLX', 'dep': '0%', 'wd': '1 VLX' },
{ 'prov': 'CRON', 'dep': '0%', 'wd': '5 CRON' },
{ 'prov': 'ONT', 'dep': '0%', 'wd': '1 ONT' },
{ 'prov': 'ONG', 'dep': '0%', 'wd': '5 ONG' },
{ 'prov': 'ALGO', 'dep': '0%', 'wd': '0.01 ALGO' },
{ 'prov': 'ATOM', 'dep': '0%', 'wd': '' },
{ 'prov': 'ATOM', 'dep': '0%', 'wd': '0.05 ATOM' },
],
},
{
'group': 'usd',
'title': 'USD',
'items': [
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'Payeer', 'dep': '3.95%', 'wd': '-' },
{ 'prov': 'AdvCash', 'dep': '0%', 'wd': '3.49%' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'AdvCash', 'dep': '0%', 'wd': '2.49%' },
{ 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5% + 0.5 USD', 'wd': '-' },
{ 'prov': 'Visa', 'dep': '3.45%', 'wd': '-' },
{ 'prov': 'Frick Bank', 'dep': '0 USD', 'wd': '-' },
@@ -389,24 +378,32 @@ module.exports = class exmo extends Exchange {
'group': 'eur',
'title': 'EUR',
'items': [
{ 'prov': 'Visa/MasterCard', 'dep': '4.5% + 0.5 EUR', 'wd': '-' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'Visa/MasterCard', 'dep': '4.5% + 0.5 EUR', 'wd': '-' },
{ 'prov': 'Visa', 'dep': '2.95%', 'wd': '-' },
{ 'prov': 'Frick Internal Transfer', 'dep': '0 EUR', 'wd': '-' },
{ 'prov': 'SEPA Frick Bank', 'dep': '0 EUR', 'wd': '-' },
{ 'prov': 'WIRE Frick Bank', 'dep': '0%', 'wd': '-' },
{ 'prov': 'SEPA Frick Bank', 'dep': '0 EUR', 'wd': '1 EUR' },
{ 'prov': 'WIRE Frick Bank', 'dep': '0%', 'wd': '20 EUR' },
{ 'prov': 'SEPA Weg Ag', 'dep': '-', 'wd': '1 EUR' },
],
},
{
'group': 'gbp',
'title': 'GBP',
'items': [
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'WIRE Frick Bank', 'dep': '10 GBP', 'wd': '-' },
],
},
{
'group': 'rub',
'title': 'RUB',
'items': [
{ 'prov': 'Payeer', 'dep': '2.49%', 'wd': '3.99%' },
{ 'prov': 'Payeer', 'dep': '2.49%', 'wd': '3.49%' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'Qiwi', 'dep': '1.49%', 'wd': '2.49%' },
{ 'prov': 'Yandex Money', 'dep': '1.49%', 'wd': '-' },
{ 'prov': 'AdvCash', 'dep': '0%', 'wd': '3.49%' },
{ 'prov': 'Yandex Money', 'dep': '1.49%', 'wd': '1.95 %' },
{ 'prov': 'AdvCash', 'dep': '0.99%', 'wd': '0.99%' },
{ 'prov': 'Visa/MasterCard', 'dep': '2.99%', 'wd': '3.99% + 60 RUB' },
],
},
@@ -433,17 +430,16 @@ module.exports = class exmo extends Exchange {
'items': [
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'Terminal', 'dep': '2.6%', 'wd': '-' },
{ 'prov': 'Visa/MasterCard', 'dep': '1% + 5 UAH', 'wd': '-' },
{ 'prov': 'Visa/MasterCard EasyTransfer', 'dep': '-', 'wd': '2.99%' },
{ 'prov': 'Visa/MasterCard', 'dep': '1% + 5 UAH', 'wd': '-' },
],
},
{
'group': 'kzt',
'title': 'KZT',
'items': [
{ 'prov': 'Visa/MasterCard', 'dep': '3.5%', 'wd': '2.99% + 450 KZT' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'Visa/MasterCard RoyalPay', 'dep': '-', 'wd': '2.99% + 450 KZT' },
{ 'prov': 'Visa/MasterCard', 'dep': '3.5%', 'wd': '-' },
{ 'prov': 'AdvCash', 'dep': '0%', 'wd': '-' },
],
},
@@ -464,6 +460,7 @@ module.exports = class exmo extends Exchange {
'50054': InsufficientFunds,
'50304': OrderNotFound, // "Order was not found '123456789'" (fetching order trades for an order that does not have trades yet)
'50173': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order)
'50277': InvalidOrder,
'50319': InvalidOrder, // Price by order is less than permissible minimum for this pair
'50321': InvalidOrder, // Price by order is more than permissible maximum for this pair
},
@@ -473,6 +470,7 @@ module.exports = class exmo extends Exchange {
'API rate limit exceeded': RateLimitExceeded, // {"result":false,"error":"API rate limit exceeded for 99.33.55.224. Retry after 60 sec.","history":[],"begin":1579392000,"end":1579478400}
},
},
'orders': {}, // orders cache / emulation
});
}
@@ -510,6 +508,9 @@ module.exports = class exmo extends Exchange {
if ((input === undefined) || (input === '-')) {
return undefined;
}
if (input === '') {
return 0;
}
const isPercentage = (input.indexOf ('%') >= 0);
const parts = input.split (' ');
const value = parts[0].replace ('%', '');
@@ -539,14 +540,10 @@ module.exports = class exmo extends Exchange {
const withdrawalFee = this.safeString (item, 'wd');
const depositFee = this.safeString (item, 'dep');
if (withdrawalFee !== undefined) {
if (withdrawalFee.length > 0) {
withdraw[code] = this.parseFixedFloatValue (withdrawalFee);
}
withdraw[code] = this.parseFixedFloatValue (withdrawalFee);
}
if (depositFee !== undefined) {
if (depositFee.length > 0) {
deposit[code] = this.parseFixedFloatValue (depositFee);
}
deposit[code] = this.parseFixedFloatValue (depositFee);
}
}
// sets fiat fees to undefined
@@ -634,15 +631,16 @@ module.exports = class exmo extends Exchange {
const response = await this.publicGetPairSettings (params);
//
// {
// "EXM_ETH": {
// "min_quantity": "1",
// "max_quantity": "1000",
// "min_price": "1",
// "max_price": "1000",
// "max_amount": "1000",
// "min_amount": "1",
// "commission_taker_percent": "0.2",
// "commission_maker_percent": "0.2"
// "BTC_USD":{
// "min_quantity":"0.0001",
// "max_quantity":"1000",
// "min_price":"1",
// "max_price":"30000",
// "max_amount":"500000",
// "min_amount":"1",
// "price_precision":8,
// "commission_taker_percent":"0.4",
// "commission_maker_percent":"0.4"
// },
// }
//
@@ -683,7 +681,7 @@ module.exports = class exmo extends Exchange {
},
'precision': {
'amount': 8,
'price': 8,
'price': this.safeInteger (market, 'price_precision'),
},
'info': market,
});
@@ -738,7 +736,7 @@ module.exports = class exmo extends Exchange {
return this.parseOHLCVs (candles, market, timeframe, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '5m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "t":1584057600000,
@@ -763,16 +761,18 @@ module.exports = class exmo extends Exchange {
await this.loadMarkets ();
const response = await this.privatePostUserInfo (params);
const result = { 'info': response };
const codes = Object.keys (this.currencies);
const free = this.safeValue (response, 'balances', {});
const used = this.safeValue (response, 'reserved', {});
const codes = Object.keys (free);
for (let i = 0; i < codes.length; i++) {
const code = codes[i];
const currencyId = this.currencyId (code);
const account = this.account ();
if (currencyId in response['balances']) {
account['free'] = this.safeFloat (response['balances'], currencyId);
if (currencyId in free) {
account['free'] = this.safeFloat (free, currencyId);
}
if (currencyId in response['reserved']) {
account['used'] = this.safeFloat (response['reserved'], currencyId);
if (currencyId in used) {
account['used'] = this.safeFloat (used, currencyId);
}
result[code] = account;
}
@@ -871,7 +871,7 @@ module.exports = class exmo extends Exchange {
const ticker = response[id];
result[symbol] = this.parseTicker (ticker, market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
@@ -1068,7 +1068,7 @@ module.exports = class exmo extends Exchange {
amount = parseFloat (amount);
price = parseFloat (price);
const status = 'open';
const order = {
return {
'id': id,
'info': response,
'timestamp': timestamp,
@@ -1088,60 +1088,45 @@ module.exports = class exmo extends Exchange {
'clientOrderId': undefined,
'average': undefined,
};
this.orders[id] = order;
return order;
}
async cancelOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = { 'order_id': id };
const response = await this.privatePostOrderCancel (this.extend (request, params));
if (id in this.orders) {
this.orders[id]['status'] = 'canceled';
}
return response;
return await this.privatePostOrderCancel (this.extend (request, params));
}
async fetchOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
try {
const request = {
'order_id': id.toString (),
};
const response = await this.privatePostOrderTrades (this.extend (request, params));
//
// {
// "type": "buy",
// "in_currency": "BTC",
// "in_amount": "1",
// "out_currency": "USD",
// "out_amount": "100",
// "trades": [
// {
// "trade_id": 3,
// "date": 1435488248,
// "type": "buy",
// "pair": "BTC_USD",
// "order_id": 12345,
// "quantity": 1,
// "price": 100,
// "amount": 100
// }
// ]
// }
//
const order = this.parseOrder (response);
return this.extend (order, {
'id': id.toString (),
});
} catch (e) {
if (e instanceof OrderNotFound) {
if (id in this.orders) {
return this.orders[id];
}
}
}
throw new OrderNotFound (this.id + ' fetchOrder order id ' + id.toString () + ' not found in cache.');
const request = {
'order_id': id.toString (),
};
const response = await this.privatePostOrderTrades (this.extend (request, params));
//
// {
// "type": "buy",
// "in_currency": "BTC",
// "in_amount": "1",
// "out_currency": "USD",
// "out_amount": "100",
// "trades": [
// {
// "trade_id": 3,
// "date": 1435488248,
// "type": "buy",
// "pair": "BTC_USD",
// "order_id": 12345,
// "quantity": 1,
// "price": 100,
// "amount": 100
// }
// ]
// }
//
const order = this.parseOrder (response);
return this.extend (order, {
'id': id.toString (),
});
}
async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
@@ -1182,48 +1167,7 @@ module.exports = class exmo extends Exchange {
return this.parseTrades (trades, market, since, limit);
}
updateCachedOrders (openOrders, symbol) {
// update local cache with open orders
for (let j = 0; j < openOrders.length; j++) {
const id = openOrders[j]['id'];
this.orders[id] = openOrders[j];
}
const openOrdersIndexedById = this.indexBy (openOrders, 'id');
const cachedOrderIds = Object.keys (this.orders);
for (let k = 0; k < cachedOrderIds.length; k++) {
// match each cached order to an order in the open orders array
// possible reasons why a cached order may be missing in the open orders array:
// - order was closed or canceled -> update cache
// - symbol mismatch (e.g. cached BTC/USDT, fetched ETH/USDT) -> skip
const id = cachedOrderIds[k];
let order = this.orders[id];
if (!(id in openOrdersIndexedById)) {
// cached order is not in open orders array
// if we fetched orders by symbol and it doesn't match the cached order -> won't update the cached order
if (symbol !== undefined && symbol !== order['symbol']) {
continue;
}
// order is cached but not present in the list of open orders -> mark the cached order as closed
if (order['status'] === 'open') {
order = this.extend (order, {
'status': 'closed', // likewise it might have been canceled externally (unnoticed by "us")
'cost': undefined,
'filled': order['amount'],
'remaining': 0.0,
});
if (order['cost'] === undefined) {
if (order['filled'] !== undefined) {
order['cost'] = order['filled'] * order['price'];
}
}
this.orders[id] = order;
}
}
}
return this.toArray (this.orders);
}
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.privatePostUserOpenOrders (params);
const marketIds = Object.keys (response);
@@ -1237,19 +1181,6 @@ module.exports = class exmo extends Exchange {
const parsedOrders = this.parseOrders (response[marketId], market);
orders = this.arrayConcat (orders, parsedOrders);
}
this.updateCachedOrders (orders, symbol);
return this.filterBySymbolSinceLimit (this.toArray (this.orders), symbol, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.fetchOrders (symbol, since, limit, params);
const orders = this.filterBy (this.orders, 'status', 'open');
return this.filterBySymbolSinceLimit (orders, symbol, since, limit);
}
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.fetchOrders (symbol, since, limit, params);
const orders = this.filterBy (this.orders, 'status', 'closed');
return this.filterBySymbolSinceLimit (orders, symbol, since, limit);
}
@@ -1341,15 +1272,15 @@ module.exports = class exmo extends Exchange {
}
lastTradeTimestamp = trades[numTransactions - 1]['timestamp'];
}
let status = this.safeString (order, 'status'); // in case we need to redefine it for canceled orders
let remaining = undefined;
if (amount !== undefined) {
remaining = amount - filled;
}
let status = this.safeString (order, 'status'); // in case we need to redefine it for canceled orders
if (filled >= amount) {
status = 'closed';
} else {
status = 'open';
if (filled >= amount) {
status = 'closed';
} else {
status = 'open';
}
}
if (market === undefined) {
market = this.getMarketFromTrades (trades);
@@ -1386,8 +1317,10 @@ module.exports = class exmo extends Exchange {
'status': status,
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, AuthenticationError, ExchangeNotAvailable } = require ('./base/errors');
const { ExchangeError, AuthenticationError, ExchangeNotAvailable, ArgumentsRequired } = require ('./base/errors');
// ---------------------------------------------------------------------------
@@ -16,9 +16,16 @@ module.exports = class exx extends Exchange {
'rateLimit': 1000 / 10,
'userAgent': this.userAgents['chrome'],
'has': {
'fetchOrder': true,
'fetchTickers': true,
'cancelOrder': true,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/37770292-fbf613d0-2de4-11e8-9f79-f2dc451b8ccb.jpg',
@@ -83,6 +90,7 @@ module.exports = class exx extends Exchange {
},
},
'commonCurrencies': {
'DOS': 'DEMOS',
'TV': 'TIV', // Ti-Value
},
'exceptions': {
@@ -194,7 +202,7 @@ module.exports = class exx extends Exchange {
};
result[symbol] = this.parseTicker (ticker, market);
}
return result;
return this.filterByArray (result, 'symbol', symbols);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
@@ -317,8 +325,10 @@ module.exports = class exx extends Exchange {
'status': status,
'symbol': symbol,
'type': 'limit',
'timeInForce': undefined,
'side': order['type'],
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
@@ -349,7 +359,6 @@ module.exports = class exx extends Exchange {
'type': side,
'info': response,
}, market);
this.orders[id] = order;
return order;
}
@@ -377,6 +386,9 @@ module.exports = class exx extends Exchange {
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument');
}
const market = this.market (symbol);
const request = {
'currency': market['id'],
@@ -20,18 +20,25 @@ module.exports = class fcoin extends Exchange {
'accountsById': undefined,
'hostname': 'fcoin.com',
'has': {
'cancelOrder': true,
'CORS': false,
'createOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchCurrencies': false,
'fetchDepositAddress': false,
'fetchMarkets': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOrderBook': true,
'fetchOrderBooks': false,
'fetchOrders': true,
'fetchTicker': true,
'fetchTime': true,
'fetchTrades': true,
'fetchTradingLimits': false,
'withdraw': false,
'fetchCurrencies': false,
},
'timeframes': {
'1m': 'M1',
@@ -375,16 +382,11 @@ module.exports = class fcoin extends Exchange {
if (tickerType !== undefined) {
const parts = tickerType.split ('.');
const id = parts[1];
if (id in this.markets_by_id) {
market = this.markets_by_id[id];
}
symbol = this.safeSymbol (id, market);
}
}
const values = ticker['ticker'];
const last = this.safeFloat (values, 0);
if (market !== undefined) {
symbol = market['symbol'];
}
return {
'symbol': symbol,
'timestamp': timestamp,
@@ -443,6 +445,17 @@ module.exports = class fcoin extends Exchange {
};
}
async fetchTime (params = {}) {
const response = await this.publicGetServerTime (params);
//
// {
// "status": 0,
// "data": 1523430502977
// }
//
return this.safeInteger (response, 'data');
}
async fetchTrades (symbol, since = undefined, limit = 50, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
@@ -533,13 +546,9 @@ module.exports = class fcoin extends Exchange {
const id = this.safeString (order, 'id');
const side = this.safeString (order, 'side');
const status = this.parseOrderStatus (this.safeString (order, 'state'));
let symbol = undefined;
if (market === undefined) {
const marketId = this.safeString (order, 'symbol');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
}
const marketId = this.safeString (order, 'symbol');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
const orderType = this.safeString (order, 'type');
const timestamp = this.safeInteger (order, 'created_at');
const amount = this.safeFloat (order, 'amount');
@@ -564,14 +573,12 @@ module.exports = class fcoin extends Exchange {
const feeRebate = this.safeFloat (order, 'fees_income');
if ((feeRebate !== undefined) && (feeRebate > 0)) {
if (market !== undefined) {
symbol = market['symbol'];
feeCurrency = (side === 'buy') ? market['quote'] : market['base'];
}
feeCost = -feeRebate;
} else {
feeCost = this.safeFloat (order, 'fill_fees');
if (market !== undefined) {
symbol = market['symbol'];
feeCurrency = (side === 'buy') ? market['base'] : market['quote'];
}
}
@@ -584,8 +591,10 @@ module.exports = class fcoin extends Exchange {
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': orderType,
'timeInForce': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'remaining': remaining,
@@ -636,7 +645,7 @@ module.exports = class fcoin extends Exchange {
return this.parseOrders (response['data'], market, since, limit);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
parseOHLCV (ohlcv, market = undefined) {
return [
this.safeTimestamp (ohlcv, 'id'),
this.safeFloat (ohlcv, 'open'),
@@ -664,7 +673,8 @@ module.exports = class fcoin extends Exchange {
request['before'] = this.sum (sinceInSeconds, timerange) - 1;
}
const response = await this.marketGetCandlesTimeframeSymbol (this.extend (request, params));
return this.parseOHLCVs (response['data'], market, timeframe, since, limit);
const data = this.safeValue (response, 'data', []);
return this.parseOHLCVs (data, market, timeframe, since, limit);
}
nonce () {
@@ -701,7 +711,7 @@ module.exports = class fcoin extends Exchange {
auth += this.urlencode (query);
}
}
const payload = this.stringToBase64 (this.encode (auth));
const payload = this.stringToBase64 (auth);
let signature = this.hmac (payload, this.encode (this.secret), 'sha1', 'binary');
signature = this.decode (this.stringToBase64 (signature));
headers = {
@@ -16,10 +16,17 @@ module.exports = class flowbtc extends Exchange {
'version': 'v1',
'rateLimit': 1000,
'has': {
'cancelOrder': true,
'CORS': false,
'createOrder': true,
'fetchBalance': true,
'fetchMarkets': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTrades': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/28162465-cd815d4c-67cf-11e7-8e57-438bea0523a2.jpg',
'logo': 'https://user-images.githubusercontent.com/51840849/87443317-01c0d080-c5fe-11ea-95c2-9ebe1a8fafd9.jpg',
'api': 'https://publicapi.flowbtc.com.br',
'www': 'https://www.flowbtc.com.br',
'doc': 'https://www.flowbtc.com.br/api.html',
Loaded 100 of 274 files, more files were not shown because too many files have changed in this diff. Show more