CCXT installed
This commit is contained in:
1 parent
182882495d
commit
384b107461
246 files changed
+298114
No files matched your search
+6
@@ -0,0 +1,6 @@
|
||||
.git
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
node_modules
|
||||
*.swp
|
||||
*.pyc
|
||||
+4
@@ -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
|
||||
+926
@@ -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>
|
||||
+21
@@ -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.
|
||||
+551
@@ -0,0 +1,551 @@
|
||||
# CCXT – CryptoCurrency eXchange Trading Library
|
||||
|
||||
[](https://travis-ci.com/ccxt/ccxt) [](https://npmjs.com/package/ccxt) [](https://pypi.python.org/pypi/ccxt) [](https://www.npmjs.com/package/ccxt) [](https://discord.gg/dhzSKYU) [](https://github.com/ccxt/ccxt/wiki/Exchange-Markets) [](https://opencollective.com/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
|
||||
|
||||
[](https://ccxt.pro) **[A JavaScript / Python / PHP cryptocurrency exchange trading WebSocket API for professionals](https://ccxt.pro)**
|
||||
|
||||
## See Also
|
||||
|
||||
- <sub>[](https://quadency.com?utm_source=ccxt)</sub> **[Quadency](https://quadency.com?utm_source=ccxt)** — trading automation terminal for leading exchanges with streaming APIs + Python IDE.
|
||||
- <sub>[](https://tab-trader.com/?utm_source=ccxt)</sub> **[TabTrader](https://tab-trader.com/?utm_source=ccxt)** — 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 |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------|:---:|:----------------------------------------------------------------------------------:|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
|
||||
| [](https://www.binance.com/?ref=10205187) | binance | [Binance](https://www.binance.com/?ref=10205187) | * | [API](https://binance-docs.github.io/apidocs/spot/en) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 1 | [API](https://docs.bitfinex.com/v1/docs) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](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) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://bitvavo.com/?a=24F34952F7) | bitvavo | [Bitvavo](https://bitvavo.com/?a=24F34952F7) | 2 | [API](https://docs.bitvavo.com/) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.byte-trade.com) | bytetrade | [ByteTrade](https://www.byte-trade.com) | * | [API](https://docs.byte-trade.com/#description) | [](https://github.com/ccxt/ccxt/wiki/Certification) | |
|
||||
| [](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) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://eterbase.exchange/invite/1wjjh4Pe) | eterbase | [Eterbase](https://eterbase.exchange/invite/1wjjh4Pe) | 1 | [API](https://developers.eterbase.exchange) | [](https://github.com/ccxt/ccxt/wiki/Certification) | |
|
||||
| [](https://ftx.com/#a=1623029) | ftx | [FTX](https://ftx.com/#a=1623029) | * | [API](https://github.com/ftexchange/ftx) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://idex.io) | idex | [IDEX](https://idex.io) | 2 | [API](https://docs.idex.io/) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.kraken.com) | kraken | [Kraken](https://www.kraken.com) | 0 | [API](https://www.kraken.com/features/api) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](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) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://waves.exchange) | wavesexchange | [Waves.Exchange](https://waves.exchange) | * | [API](https://docs.waves.exchange) | [](https://github.com/ccxt/ccxt/wiki/Certification) | |
|
||||
| [](https://xena.exchange) | xena | [Xena Exchange](https://xena.exchange) | * | [API](https://support.xena.exchange/support/solutions/44000808700) | [](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 |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|-----------------------------------------------------------------------------------------|:---:|:-------------------------------------------------------------------------------------------:|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
|
||||
| [](https://acx.io) | acx | [ACX](https://acx.io) | 2 | [API](https://acx.io/documents/api_v2) | | |
|
||||
| [](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) | | |
|
||||
| [](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) | | |
|
||||
| [](https://bequant.io) | bequant | [Bequant](https://bequant.io) | 2 | [API](https://api.bequant.io/) | | [](https://ccxt.pro) |
|
||||
| [](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/) | | |
|
||||
| [](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) | | |
|
||||
| [](https://www.binance.com/?ref=10205187) | binance | [Binance](https://www.binance.com/?ref=10205187) | * | [API](https://binance-docs.github.io/apidocs/spot/en) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](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) | | [](https://ccxt.pro) |
|
||||
| [](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) | | |
|
||||
| [](https://bitbank.cc/) | bitbank | [bitbank](https://bitbank.cc/) | 1 | [API](https://docs.bitbank.cc/) | | |
|
||||
| [](https://auth.bitbay.net/ref/jHlbB4mIkdS1) | bitbay | [BitBay](https://auth.bitbay.net/ref/jHlbB4mIkdS1) | * | [API](https://bitbay.net/public-api) | | |
|
||||
| [](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) | | [](https://ccxt.pro) |
|
||||
| [](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 1 | [API](https://docs.bitfinex.com/v1/docs) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex2 | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 2 | [API](https://docs.bitfinex.com/v2/docs/) | | |
|
||||
| [](https://bitflyer.com) | bitflyer | [bitFlyer](https://bitflyer.com) | 1 | [API](https://lightning.bitflyer.com/docs?lang=en) | | |
|
||||
| [](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) | | |
|
||||
| [](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) | | |
|
||||
| [](https://www.bithumb.com) | bithumb | [Bithumb](https://www.bithumb.com) | * | [API](https://apidocs.bithumb.com) | | |
|
||||
| [](https://www.bitkk.com) | bitkk | [bitkk](https://www.bitkk.com) | 1 | [API](https://www.bitkk.com/i/developer) | | |
|
||||
| [](http://www.bitmart.com/?r=rQCFLh) | bitmart | [BitMart](http://www.bitmart.com/?r=rQCFLh) | 1 | [API](https://github.com/bitmartexchange/bitmart-official-api-docs) | | |
|
||||
| [](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) | | |
|
||||
| [](https://www.bitmex.com/register/upZpOX) | bitmex | [BitMEX](https://www.bitmex.com/register/upZpOX) | 1 | [API](https://www.bitmex.com/app/apiOverview) | | [](https://ccxt.pro) |
|
||||
| [](https://www.bitpanda.com/en/pro) | bitpanda | [Bitpanda Pro](https://www.bitpanda.com/en/pro) | 1 | [API](https://developers.bitpanda.com/exchange/) | | |
|
||||
| [](https://bitso.com/?ref=itej) | bitso | [Bitso](https://bitso.com/?ref=itej) | 3 | [API](https://bitso.com/api_info) | | |
|
||||
| [](https://www.bitstamp.net) | bitstamp | [Bitstamp](https://www.bitstamp.net) | 2 | [API](https://www.bitstamp.net/api) | | [](https://ccxt.pro) |
|
||||
| [](https://www.bitstamp.net) | bitstamp1 | [Bitstamp](https://www.bitstamp.net) | 1 | [API](https://www.bitstamp.net/api) | | |
|
||||
| [](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) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://bitvavo.com/?a=24F34952F7) | bitvavo | [Bitvavo](https://bitvavo.com/?a=24F34952F7) | 2 | [API](https://docs.bitvavo.com/) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](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/) | | |
|
||||
| [](https://bl3p.eu) | bl3p | [BL3P](https://bl3p.eu) | 1 | [API](https://github.com/BitonicNL/bl3p-api/tree/master/docs) | | |
|
||||
| [](https://bleutrade.com) | bleutrade | [Bleutrade](https://bleutrade.com) | * | [API](https://app.swaggerhub.com/apis-docs/bleu/white-label/3.0.0) | | |
|
||||
| [](https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465) | braziliex | [Braziliex](https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465) | * | [API](https://braziliex.com/exchange/api.php) | | |
|
||||
| [](https://btc-alpha.com/?r=123788) | btcalpha | [BTC-Alpha](https://btc-alpha.com/?r=123788) | 1 | [API](https://btc-alpha.github.io/api-docs) | | |
|
||||
| [](https://www.btcbox.co.jp/) | btcbox | [BtcBox](https://www.btcbox.co.jp/) | 1 | [API](https://blog.btcbox.jp/en/archives/8762) | | |
|
||||
| [](https://btcmarkets.net) | btcmarkets | [BTC Markets](https://btcmarkets.net) | 3 | [API](https://api.btcmarkets.net/doc/v3) | | |
|
||||
| [](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) | | |
|
||||
| [](https://www.btcturk.com) | btcturk | [BTCTurk](https://www.btcturk.com) | * | [API](https://github.com/BTCTrader/broker-api-docs) | | |
|
||||
| [](https://www.buda.com) | buda | [Buda](https://www.buda.com) | 2 | [API](https://api.buda.com) | | |
|
||||
| [](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) | | |
|
||||
| [](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/) | | |
|
||||
| [](https://www.byte-trade.com) | bytetrade | [ByteTrade](https://www.byte-trade.com) | * | [API](https://docs.byte-trade.com/#description) | [](https://github.com/ccxt/ccxt/wiki/Certification) | |
|
||||
| [](https://cex.io/r/0/up105393824/0/) | cex | [CEX.IO](https://cex.io/r/0/up105393824/0/) | * | [API](https://cex.io/cex-api) | | |
|
||||
| [](https://chilebit.net) | chilebit | [ChileBit](https://chilebit.net) | 1 | [API](https://blinktrade.com/docs) | | |
|
||||
| [](https://www.coinbase.com/join/58cbe25a355148797479dbd2) | coinbase | [Coinbase](https://www.coinbase.com/join/58cbe25a355148797479dbd2) | 2 | [API](https://developers.coinbase.com/api/v2) | | |
|
||||
| [](https://prime.coinbase.com) | coinbaseprime | [Coinbase Prime](https://prime.coinbase.com) | * | [API](https://docs.prime.coinbase.com) | | [](https://ccxt.pro) |
|
||||
| [](https://pro.coinbase.com/) | coinbasepro | [Coinbase Pro](https://pro.coinbase.com/) | * | [API](https://docs.pro.coinbase.com) | | [](https://ccxt.pro) |
|
||||
| [](https://coincheck.com) | coincheck | [coincheck](https://coincheck.com) | * | [API](https://coincheck.com/documents/exchange/api) | | |
|
||||
| [](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) | | |
|
||||
| [](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) | | |
|
||||
| [](https://coinfalcon.com/?ref=CFJSVGTUPASB) | coinfalcon | [CoinFalcon](https://coinfalcon.com/?ref=CFJSVGTUPASB) | 1 | [API](https://docs.coinfalcon.com) | | |
|
||||
| [](https://www.coinfloor.co.uk) | coinfloor | [coinfloor](https://www.coinfloor.co.uk) | * | [API](https://github.com/coinfloor/api) | | |
|
||||
| [](https://www.coingi.com/?r=XTPPMC) | coingi | [Coingi](https://www.coingi.com/?r=XTPPMC) | * | [API](https://coingi.docs.apiary.io) | | |
|
||||
| [](https://coinmarketcap.com) | coinmarketcap | [CoinMarketCap](https://coinmarketcap.com) | 1 | [API](https://coinmarketcap.com/api) | | |
|
||||
| [](https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0) | coinmate | [CoinMate](https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0) | * | [API](https://coinmate.docs.apiary.io) | | |
|
||||
| [](https://coinone.co.kr) | coinone | [CoinOne](https://coinone.co.kr) | 2 | [API](https://doc.coinone.co.kr) | | |
|
||||
| [](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) | | |
|
||||
| [](https://crex24.com/?refid=slxsjsjtil8xexl9hksr) | crex24 | [CREX24](https://crex24.com/?refid=slxsjsjtil8xexl9hksr) | 2 | [API](https://docs.crex24.com/trade-api/v2) | | |
|
||||
| [](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) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.deribit.com/reg-1189.4038) | deribit | [Deribit](https://www.deribit.com/reg-1189.4038) | 2 | [API](https://docs.deribit.com/v2) | | |
|
||||
| [](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) | | |
|
||||
| [](https://dsxglobal.com) | dsx | [DSX](https://dsxglobal.com) | 3 | [API](https://dsxglobal.com/developers/publicApi) | | |
|
||||
| [](https://eterbase.exchange/invite/1wjjh4Pe) | eterbase | [Eterbase](https://eterbase.exchange/invite/1wjjh4Pe) | 1 | [API](https://developers.eterbase.exchange) | [](https://github.com/ccxt/ccxt/wiki/Certification) | |
|
||||
| [](https://exmo.me/?ref=131685) | exmo | [EXMO](https://exmo.me/?ref=131685) | 1.1 | [API](https://exmo.me/en/api_doc?ref=131685) | | |
|
||||
| [](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) | | |
|
||||
| [](https://www.fcoin.com/i/Z5P7V) | fcoin | [FCoin](https://www.fcoin.com/i/Z5P7V) | 2 | [API](https://developer.fcoin.com) | | |
|
||||
| [](https://www.fcoinjp.com) | fcoinjp | [FCoinJP](https://www.fcoinjp.com) | 2 | [API](https://developer.fcoin.com) | | |
|
||||
| [](https://www.flowbtc.com.br) | flowbtc | [flowBTC](https://www.flowbtc.com.br) | 1 | [API](https://www.flowbtc.com.br/api.html) | | |
|
||||
| [](https://foxbit.com.br/exchange) | foxbit | [FoxBit](https://foxbit.com.br/exchange) | 1 | [API](https://foxbit.com.br/api/) | | |
|
||||
| [](https://ftx.com/#a=1623029) | ftx | [FTX](https://ftx.com/#a=1623029) | * | [API](https://github.com/ftexchange/ftx) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.gate.io/signup/2436035) | gateio | [Gate.io](https://www.gate.io/signup/2436035) | 2 | [API](https://gate.io/api2) | | [](https://ccxt.pro) |
|
||||
| [](https://gemini.com/) | gemini | [Gemini](https://gemini.com/) | 1 | [API](https://docs.gemini.com/rest-api) | | |
|
||||
| [](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) | | |
|
||||
| [](https://hitbtc.com/?ref_id=5a5d39a65d466) | hitbtc | [HitBTC](https://hitbtc.com/?ref_id=5a5d39a65d466) | 2 | [API](https://api.hitbtc.com) | | [](https://ccxt.pro) |
|
||||
| [](https://pro.hollaex.com/signup?affiliation_code=QSWA6G) | hollaex | [HollaEx](https://pro.hollaex.com/signup?affiliation_code=QSWA6G) | 1 | [API](https://apidocs.hollaex.com) | | |
|
||||
| [](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) | | [](https://ccxt.pro) |
|
||||
| [](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/) | | [](https://ccxt.pro) |
|
||||
| [](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) | | [](https://ccxt.pro) |
|
||||
| [](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) | | |
|
||||
| [](https://idex.io) | idex | [IDEX](https://idex.io) | 2 | [API](https://docs.idex.io/) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.independentreserve.com) | independentreserve | [Independent Reserve](https://www.independentreserve.com) | * | [API](https://www.independentreserve.com/API) | | |
|
||||
| [](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) | | |
|
||||
| [](https://www.itbit.com) | itbit | [itBit](https://www.itbit.com) | 1 | [API](https://api.itbit.com/docs) | | |
|
||||
| [](https://www.kraken.com) | kraken | [Kraken](https://www.kraken.com) | 0 | [API](https://www.kraken.com/features/api) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.kucoin.com/?rcode=E5wkqe) | kucoin | [KuCoin](https://www.kucoin.com/?rcode=E5wkqe) | 2 | [API](https://docs.kucoin.com) | | [](https://ccxt.pro) |
|
||||
| [](https://kuna.io?r=kunaid-gvfihe8az7o4) | kuna | [Kuna](https://kuna.io?r=kunaid-gvfihe8az7o4) | 2 | [API](https://kuna.io/documents/api) | | |
|
||||
| [](https://www.lakebtc.com) | lakebtc | [LakeBTC](https://www.lakebtc.com) | 2 | [API](https://www.lakebtc.com/s/api_v2) | | |
|
||||
| [](https://latoken.com) | latoken | [Latoken](https://latoken.com) | 1 | [API](https://api.latoken.com) | | |
|
||||
| [](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) | | |
|
||||
| [](https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976) | liquid | [Liquid](https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976) | 2 | [API](https://developers.liquid.com) | | |
|
||||
| [](https://livecoin.net/?from=Livecoin-CQ1hfx44) | livecoin | [LiveCoin](https://livecoin.net/?from=Livecoin-CQ1hfx44) | * | [API](https://www.livecoin.net/api?lang=en) | | |
|
||||
| [](https://www.luno.com/invite/44893A) | luno | [luno](https://www.luno.com/invite/44893A) | 1 | [API](https://www.luno.com/en/api) | | |
|
||||
| [](https://www.lykke.com) | lykke | [Lykke](https://www.lykke.com) | 1 | [API](https://hft-api.lykke.com/swagger/ui/) | | |
|
||||
| [](https://www.mercadobitcoin.com.br) | mercado | [Mercado Bitcoin](https://www.mercadobitcoin.com.br) | 3 | [API](https://www.mercadobitcoin.com.br/api-doc) | | |
|
||||
| [](https://mixcoins.com) | mixcoins | [MixCoins](https://mixcoins.com) | 1 | [API](https://mixcoins.com/help/api/) | | |
|
||||
| [](https://www.novadax.com.br/?s=ccxt) | novadax | [NovaDAX](https://www.novadax.com.br/?s=ccxt) | 1 | [API](https://doc.novadax.com/pt-BR/) | | |
|
||||
| [](https://oceanex.pro/signup?referral=VE24QX) | oceanex | [OceanEx](https://oceanex.pro/signup?referral=VE24QX) | 1 | [API](https://api.oceanex.pro/doc/v1) | | |
|
||||
| [](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/) | | [](https://ccxt.pro) |
|
||||
| [](https://www.okex.com/join/1888677) | okex | [OKEX](https://www.okex.com/join/1888677) | 3 | [API](https://www.okex.com/docs/en/) | | [](https://ccxt.pro) |
|
||||
| [](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) | | |
|
||||
| [](https://phemex.com/register?referralCode=EDNVJ) | phemex | [Phemex](https://phemex.com/register?referralCode=EDNVJ) | 1 | [API](https://github.com/phemex/phemex-api-docs) | | [](https://ccxt.pro) |
|
||||
| [](https://poloniex.com/signup?c=UBFZJRPJ) | poloniex | [Poloniex](https://poloniex.com/signup?c=UBFZJRPJ) | * | [API](https://docs.poloniex.com) | | [](https://ccxt.pro) |
|
||||
| [](https://www.probit.com/r/34608773) | probit | [ProBit](https://www.probit.com/r/34608773) | 1 | [API](https://docs-en.probit.com) | | |
|
||||
| [](https://qtrade.io/?ref=BKOQWVFGRH2C) | qtrade | [qTrade](https://qtrade.io/?ref=BKOQWVFGRH2C) | 1 | [API](https://qtrade-exchange.github.io/qtrade-docs) | | |
|
||||
| [](https://www.rightbtc.com) | rightbtc | [RightBTC](https://www.rightbtc.com) | * | [API](https://docs.rightbtc.com/api/) | | |
|
||||
| [](https://exchange.ripio.com) | ripio | [Ripio](https://exchange.ripio.com) | 1 | [API](https://exchange.ripio.com/en/api/) | | [](https://ccxt.pro) |
|
||||
| [](https://www.southxchange.com) | southxchange | [SouthXchange](https://www.southxchange.com) | * | [API](https://www.southxchange.com/Home/Api) | | |
|
||||
| [](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) | | |
|
||||
| [](https://surbitcoin.com) | surbitcoin | [SurBitcoin](https://surbitcoin.com) | 1 | [API](https://blinktrade.com/docs) | | |
|
||||
| [](https://therocktrading.com) | therock | [TheRockTrading](https://therocktrading.com) | 1 | [API](https://api.therocktrading.com/doc/v1/index.html) | | |
|
||||
| [](http://bit.ly/2IX0LrM) | tidebit | [TideBit](http://bit.ly/2IX0LrM) | 2 | [API](https://www.tidebit.com/documents/api/guide) | | |
|
||||
| [](https://tidex.com/exchange/?ref=57f5638d9cd7) | tidex | [Tidex](https://tidex.com/exchange/?ref=57f5638d9cd7) | 3 | [API](https://tidex.com/exchange/public-api) | | |
|
||||
| [](https://timex.io/?refcode=1x27vNkTbP1uwkCck) | timex | [TimeX](https://timex.io/?refcode=1x27vNkTbP1uwkCck) | 1 | [API](https://docs.timex.io) | | |
|
||||
| [](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) | [](https://github.com/ccxt/ccxt/wiki/Certification) | [](https://ccxt.pro) |
|
||||
| [](https://www.vaultoro.com) | vaultoro | [Vaultoro](https://www.vaultoro.com) | 1 | [API](https://api.vaultoro.com) | | |
|
||||
| [](https://vbtc.exchange) | vbtc | [VBTC](https://vbtc.exchange) | 1 | [API](https://blinktrade.com/docs) | | |
|
||||
| [](https://vcc.exchange?ref=l4xhrH) | vcc | [VCC Exchange](https://vcc.exchange?ref=l4xhrH) | 3 | [API](https://vcc.exchange/api) | | |
|
||||
| [](https://waves.exchange) | wavesexchange | [Waves.Exchange](https://waves.exchange) | * | [API](https://docs.waves.exchange) | [](https://github.com/ccxt/ccxt/wiki/Certification) | |
|
||||
| [](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) | | |
|
||||
| [](https://xbtce.com/?agent=XX97BTCXXXG687021000B) | xbtce | [xBTCe](https://xbtce.com/?agent=XX97BTCXXXG687021000B) | 1 | [API](https://www.xbtce.com/tradeapi) | | |
|
||||
| [](https://xena.exchange) | xena | [Xena Exchange](https://xena.exchange) | * | [API](https://support.xena.exchange/support/solutions/44000808700) | [](https://github.com/ccxt/ccxt/wiki/Certification) | |
|
||||
| [](https://www.yobit.net) | yobit | [YoBit](https://www.yobit.net) | 3 | [API](https://www.yobit.net/en/api/) | | |
|
||||
| [](https://zaif.jp) | zaif | [Zaif](https://zaif.jp) | 1 | [API](https://techbureau-api-document.readthedocs.io/ja/latest/index.html) | | |
|
||||
| [](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.72/dist/ccxt.browser.js
|
||||
* unpkg: https://unpkg.com/ccxt@1.38.72/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.72/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>[](https://discord.gg/dhzSKYU)</sub></sub>
|
||||
|
||||
## Contact Us
|
||||
|
||||
For business inquiries: info@ccxt.trade
|
||||
Generated
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
"use strict"
|
||||
|
||||
const { execSync } = require ('child_process')
|
||||
const log = require ('ololog').noLocate
|
||||
const { groupBy } = require ('../ccxt.js')
|
||||
const { values } = Object
|
||||
const assert = require ('assert')
|
||||
|
||||
function cleanupOldTags () {
|
||||
|
||||
const tags = execSync ('git tag').toString ().split ('\n').filter (s => s).map (t => {
|
||||
|
||||
const [major, minor, patch] = t.replace ('v', '').split ('.').map (Number)
|
||||
|
||||
assert (major < 100)
|
||||
assert (minor < 100)
|
||||
|
||||
return {
|
||||
key: (major * 100) + minor,
|
||||
tag: t,
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
}
|
||||
})
|
||||
|
||||
const tagsByMajorMinor = values (groupBy (tags, 'key')).sort ((a, b) => a[0].key - b[0].key)
|
||||
|
||||
// Preserve all tags for first 5 minor versions
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
|
||||
const tags = tagsByMajorMinor.pop ()
|
||||
|
||||
if (tags) {
|
||||
log.green.bright ('Preserving', tags[0].tag, '...', tags[tags.length - 1].tag)
|
||||
}
|
||||
}
|
||||
|
||||
// For older versions, leave only "round" numbered versions (1/10th)
|
||||
|
||||
let tagsToDelete = []
|
||||
|
||||
for (const tags of tagsByMajorMinor) {
|
||||
|
||||
for (const { tag, patch } of tags) {
|
||||
|
||||
if (patch === 1) {
|
||||
log.green ('Preserving', tag)
|
||||
|
||||
} else {
|
||||
tagsToDelete.push (tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.bright.red ('Deleting', tagsToDelete.length, 'tags...')
|
||||
|
||||
if (!process.argv.includes ('--paper')) {
|
||||
|
||||
/* If it happens on a CI server, we don't want it to fail the build because of a super
|
||||
long execution time (one tag deletion takes ~5 sec...), hence that limit here */
|
||||
|
||||
if (process.argv.includes ('--limit')) {
|
||||
tagsToDelete = tagsToDelete.slice (-500)
|
||||
}
|
||||
|
||||
for (const tag of tagsToDelete) {
|
||||
|
||||
log.dim ('Deleting', tag)
|
||||
execSync (`git tag -d ${tag} && git push origin :refs/tags/${tag}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// main entry point
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
// if called directly like `node module`
|
||||
|
||||
cleanupOldTags ()
|
||||
|
||||
} else {
|
||||
|
||||
// do nothing if required as a module
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
module.exports = {
|
||||
cleanupOldTags,
|
||||
}
|
||||
Generated
Vendored
+256
@@ -0,0 +1,256 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
|
||||
AF: 'Afghanistan',
|
||||
AX: 'Åland Islands',
|
||||
AL: 'Albania',
|
||||
DZ: 'Algeria',
|
||||
AS: 'American Samoa',
|
||||
AD: 'Andorra',
|
||||
AO: 'Angola',
|
||||
AI: 'Anguilla',
|
||||
AQ: 'Antarctica',
|
||||
AG: 'Antigua and Barbuda',
|
||||
AR: 'Argentina',
|
||||
AM: 'Armenia',
|
||||
AW: 'Aruba',
|
||||
AU: 'Australia',
|
||||
AT: 'Austria',
|
||||
AZ: 'Azerbaijan',
|
||||
BS: 'Bahamas',
|
||||
BH: 'Bahrain',
|
||||
BD: 'Bangladesh',
|
||||
BB: 'Barbados',
|
||||
BY: 'Belarus',
|
||||
BE: 'Belgium',
|
||||
BZ: 'Belize',
|
||||
BJ: 'Benin',
|
||||
BM: 'Bermuda',
|
||||
BT: 'Bhutan',
|
||||
BO: 'Bolivia',
|
||||
BQ: 'Bonaire, Sint Eustatius and Saba',
|
||||
BA: 'Bosnia and Herzegovina',
|
||||
BW: 'Botswana',
|
||||
BV: 'Bouvet Island',
|
||||
BR: 'Brazil',
|
||||
IO: 'British Indian Ocean Territory',
|
||||
BN: 'Brunei Darussalam',
|
||||
BG: 'Bulgaria',
|
||||
BF: 'Burkina Faso',
|
||||
BI: 'Burundi',
|
||||
KH: 'Cambodia',
|
||||
CM: 'Cameroon',
|
||||
CA: 'Canada',
|
||||
CV: 'Cabo Verde',
|
||||
KY: 'Cayman Islands',
|
||||
CF: 'Central African Republic',
|
||||
TD: 'Chad',
|
||||
CL: 'Chile',
|
||||
CN: 'China',
|
||||
CX: 'Christmas Island',
|
||||
CC: 'Cocos (Keeling) Islands',
|
||||
CO: 'Colombia',
|
||||
KM: 'Comoros',
|
||||
CG: 'Congo',
|
||||
CD: 'Congo',
|
||||
CK: 'Cook Islands',
|
||||
CR: 'Costa Rica',
|
||||
CI: 'Côte d\'Ivoire',
|
||||
HR: 'Croatia',
|
||||
CU: 'Cuba',
|
||||
CW: 'Curaçao',
|
||||
CY: 'Cyprus',
|
||||
CZ: 'Czech Republic',
|
||||
DK: 'Denmark',
|
||||
DJ: 'Djibouti',
|
||||
DM: 'Dominica',
|
||||
DO: 'Dominican Republic',
|
||||
EC: 'Ecuador',
|
||||
EG: 'Egypt',
|
||||
SV: 'El Salvador',
|
||||
GQ: 'Equatorial Guinea',
|
||||
ER: 'Eritrea',
|
||||
EE: 'Estonia',
|
||||
ET: 'Ethiopia',
|
||||
EU: 'EU',
|
||||
FK: 'Falkland Islands',
|
||||
FO: 'Faroe Islands',
|
||||
FJ: 'Fiji',
|
||||
FI: 'Finland',
|
||||
FR: 'France',
|
||||
GF: 'French Guiana',
|
||||
PF: 'French Polynesia',
|
||||
TF: 'French Southern Territories',
|
||||
GA: 'Gabon',
|
||||
GM: 'Gambia',
|
||||
GE: 'Georgia',
|
||||
DE: 'Germany',
|
||||
GH: 'Ghana',
|
||||
GI: 'Gibraltar',
|
||||
GR: 'Greece',
|
||||
GL: 'Greenland',
|
||||
GD: 'Grenada',
|
||||
GP: 'Guadeloupe',
|
||||
GU: 'Guam',
|
||||
GT: 'Guatemala',
|
||||
GG: 'Guernsey',
|
||||
GN: 'Guinea',
|
||||
GW: 'Guinea-Bissau',
|
||||
GY: 'Guyana',
|
||||
HT: 'Haiti',
|
||||
HM: 'Heard Island and McDonald Islands',
|
||||
VA: 'Holy See',
|
||||
HN: 'Honduras',
|
||||
HK: 'Hong Kong',
|
||||
HU: 'Hungary',
|
||||
IS: 'Iceland',
|
||||
IN: 'India',
|
||||
ID: 'Indonesia',
|
||||
IR: 'Iran',
|
||||
IQ: 'Iraq',
|
||||
IE: 'Ireland',
|
||||
IM: 'Isle of Man',
|
||||
IL: 'Israel',
|
||||
IT: 'Italy',
|
||||
JM: 'Jamaica',
|
||||
JP: 'Japan',
|
||||
JE: 'Jersey',
|
||||
JO: 'Jordan',
|
||||
KZ: 'Kazakhstan',
|
||||
KE: 'Kenya',
|
||||
KI: 'Kiribati',
|
||||
KP: 'North Korea (DPRK)',
|
||||
KR: 'South Korea',
|
||||
KW: 'Kuwait',
|
||||
KG: 'Kyrgyzstan',
|
||||
LA: 'Lao People\'s Democratic Republic',
|
||||
LV: 'Latvia',
|
||||
LB: 'Lebanon',
|
||||
LS: 'Lesotho',
|
||||
LR: 'Liberia',
|
||||
LY: 'Libya',
|
||||
LI: 'Liechtenstein',
|
||||
LT: 'Lithuania',
|
||||
LU: 'Luxembourg',
|
||||
MO: 'Macao',
|
||||
MK: 'Macedonia',
|
||||
MG: 'Madagascar',
|
||||
MW: 'Malawi',
|
||||
MY: 'Malaysia',
|
||||
MV: 'Maldives',
|
||||
ML: 'Mali',
|
||||
MT: 'Malta',
|
||||
MH: 'Marshall Islands',
|
||||
MQ: 'Martinique',
|
||||
MR: 'Mauritania',
|
||||
MU: 'Mauritius',
|
||||
YT: 'Mayotte',
|
||||
MX: 'Mexico',
|
||||
FM: 'Micronesia (Federated States of)',
|
||||
MD: 'Moldova',
|
||||
MC: 'Monaco',
|
||||
MN: 'Mongolia',
|
||||
ME: 'Montenegro',
|
||||
MS: 'Montserrat',
|
||||
MA: 'Morocco',
|
||||
MZ: 'Mozambique',
|
||||
MM: 'Myanmar',
|
||||
NA: 'Namibia',
|
||||
NR: 'Nauru',
|
||||
NP: 'Nepal',
|
||||
NL: 'Netherlands',
|
||||
NC: 'New Caledonia',
|
||||
NZ: 'New Zealand',
|
||||
NI: 'Nicaragua',
|
||||
NE: 'Niger',
|
||||
NG: 'Nigeria',
|
||||
NU: 'Niue',
|
||||
NF: 'Norfolk Island',
|
||||
MP: 'Northern Mariana Islands',
|
||||
NO: 'Norway',
|
||||
OM: 'Oman',
|
||||
PK: 'Pakistan',
|
||||
PW: 'Palau',
|
||||
PS: 'Palestine, State of',
|
||||
PA: 'Panama',
|
||||
PG: 'Papua New Guinea',
|
||||
PY: 'Paraguay',
|
||||
PE: 'Peru',
|
||||
PH: 'Philippines',
|
||||
PN: 'Pitcairn',
|
||||
PL: 'Poland',
|
||||
PT: 'Portugal',
|
||||
PR: 'Puerto Rico',
|
||||
QA: 'Qatar',
|
||||
RE: 'Réunion',
|
||||
RO: 'Romania',
|
||||
RU: 'Russia',
|
||||
RW: 'Rwanda',
|
||||
BL: 'Saint Barthélemy',
|
||||
SH: 'Saint Helena, Ascension and Tristan da Cunha',
|
||||
KN: 'Saint Kitts and Nevis',
|
||||
LC: 'Saint Lucia',
|
||||
MF: 'Saint Martin (French)',
|
||||
PM: 'Saint Pierre and Miquelon',
|
||||
VC: 'St. Vincent & Grenadines',
|
||||
WS: 'Samoa',
|
||||
SM: 'San Marino',
|
||||
ST: 'Sao Tome and Principe',
|
||||
SA: 'Saudi Arabia',
|
||||
SN: 'Senegal',
|
||||
RS: 'Serbia',
|
||||
SC: 'Seychelles',
|
||||
SL: 'Sierra Leone',
|
||||
SG: 'Singapore',
|
||||
SX: 'Sint Maarten (Dutch)',
|
||||
SK: 'Slovakia',
|
||||
SI: 'Slovenia',
|
||||
SB: 'Solomon Islands',
|
||||
SO: 'Somalia',
|
||||
ZA: 'South Africa',
|
||||
GS: 'South Georgia and the South Sandwich Islands',
|
||||
SS: 'South Sudan',
|
||||
ES: 'Spain',
|
||||
LK: 'Sri Lanka',
|
||||
SD: 'Sudan',
|
||||
SR: 'Suriname',
|
||||
SJ: 'Svalbard and Jan Mayen',
|
||||
SZ: 'Swaziland',
|
||||
SE: 'Sweden',
|
||||
CH: 'Switzerland',
|
||||
SY: 'Syrian Arab Republic',
|
||||
TW: 'Taiwan',
|
||||
TJ: 'Tajikistan',
|
||||
TZ: 'Tanzania',
|
||||
TH: 'Thailand',
|
||||
TL: 'Timor-Leste',
|
||||
TG: 'Togo',
|
||||
TK: 'Tokelau',
|
||||
TO: 'Tonga',
|
||||
TT: 'Trinidad and Tobago',
|
||||
TN: 'Tunisia',
|
||||
TR: 'Turkey',
|
||||
TM: 'Turkmenistan',
|
||||
TC: 'Turks and Caicos Islands',
|
||||
TV: 'Tuvalu',
|
||||
UG: 'Uganda',
|
||||
UA: 'Ukraine',
|
||||
AE: 'United Arab Emirates',
|
||||
GB: 'UK',
|
||||
UK: 'UK',
|
||||
US: 'US',
|
||||
UM: 'US Minor Islands',
|
||||
UY: 'Uruguay',
|
||||
UZ: 'Uzbekistan',
|
||||
VU: 'Vanuatu',
|
||||
VE: 'Venezuela',
|
||||
VN: 'Vietnam',
|
||||
VG: 'British Virgin Islands',
|
||||
VI: 'US Virgin Islands',
|
||||
WF: 'Wallis and Futuna',
|
||||
EH: 'Western Sahara',
|
||||
YE: 'Yemen',
|
||||
ZM: 'Zambia',
|
||||
ZW: 'Zimbabwe',
|
||||
}
|
||||
Generated
Vendored
+454
@@ -0,0 +1,454 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Usage:
|
||||
//
|
||||
// npm run export-exchanges
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require ('fs')
|
||||
, countries = require ('./countries')
|
||||
, asTable = require ('as-table').configure ({
|
||||
delimiter: '|',
|
||||
print: (x) => ' ' + x + ' '
|
||||
})
|
||||
, execSync = require ('child_process').execSync
|
||||
, log = require ('ololog').unlimited
|
||||
, ansi = require ('ansicolor').nice
|
||||
, { keys, values, entries } = Object
|
||||
, { replaceInFile } = require ('./fs.js')
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function cloneGitHubWiki (gitWikiPath) {
|
||||
|
||||
if (!fs.existsSync (gitWikiPath)) {
|
||||
log.bright.cyan ('Cloning ccxt.wiki...')
|
||||
execSync ('git clone https://github.com/ccxt/ccxt.wiki.git ' + gitWikiPath)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function logExportExchanges (filename, regex, replacement) {
|
||||
log.bright.cyan ('Exporting exchanges →', filename.yellow)
|
||||
replaceInFile (filename, regex, replacement)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function getIncludedExchangeIds () {
|
||||
|
||||
const includedIds = fs.readFileSync ('exchanges.cfg')
|
||||
.toString () // Buffer → String
|
||||
.split ('\n') // String → Array
|
||||
.map (line => line.split ('#')[0].trim ()) // trim comments
|
||||
.filter (exchange => exchange); // filter empty lines
|
||||
|
||||
const isIncluded = (id) => ((includedIds.length === 0) || includedIds.includes (id))
|
||||
|
||||
const ids = fs.readdirSync ('./js/')
|
||||
.filter (file => file.match (/[a-zA-Z0-9_-]+.js$/))
|
||||
.map (file => file.slice (0, -3))
|
||||
.filter (isIncluded);
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function exportExchanges (replacements) {
|
||||
|
||||
log.bright.yellow ('Exporting exchanges...')
|
||||
|
||||
replacements.forEach (({ file, regex, replacement }) => {
|
||||
logExportExchanges (file, regex, replacement)
|
||||
})
|
||||
|
||||
log.bright.green ('Base sources updated successfully.')
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function createExchanges (ids) {
|
||||
|
||||
const ccxt = require ('../ccxt.js')
|
||||
|
||||
const createExchange = (id) => {
|
||||
ccxt[id].prototype.checkRequiredDependencies = () => {} // suppress it
|
||||
return new (ccxt)[id] ()
|
||||
}
|
||||
|
||||
return ccxt.indexBy (ids.map (createExchange), 'id')
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const ccxtCertifiedBadge = '[](https://github.com/ccxt/ccxt/wiki/Certification)'
|
||||
, ccxtProBadge = '[](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': '[](' + 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': '[](' + 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")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function exportSupportedAndCertifiedExchanges (exchanges, { allExchangesPaths, certifiedExchangesPaths, exchangesByCountriesPaths, proExchangesPaths }) {
|
||||
|
||||
const exchangesNotListedInDocs = [ 'hitbtc2' ]
|
||||
|
||||
const arrayOfExchanges = values (exchanges)
|
||||
|
||||
if (allExchangesPaths) {
|
||||
|
||||
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, allExchangesReplacement)
|
||||
}
|
||||
}
|
||||
|
||||
if (proExchangesPaths) {
|
||||
|
||||
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, proExchangesReplacement)
|
||||
}
|
||||
}
|
||||
|
||||
if (certifiedExchangesPaths) {
|
||||
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, certifiedExchangesReplacement)
|
||||
}
|
||||
}
|
||||
|
||||
if (exchangesByCountriesPaths) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function exportExchangeIdsToExchangesJson (exchanges) {
|
||||
log.bright ('Exporting exchange ids to'.cyan, 'exchanges.json'.yellow)
|
||||
const ids = keys (exchanges)
|
||||
console.log (ids)
|
||||
fs.writeFileSync ('exchanges.json', JSON.stringify ({ ids }, null, 4))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function exportWikiToGitHub (wikiPath, gitWikiPath) {
|
||||
|
||||
log.bright.cyan ('Exporting wiki to GitHub')
|
||||
|
||||
const ccxtWikiFiles = {
|
||||
'README.md': 'Home.md',
|
||||
'Install.md': 'Install.md',
|
||||
'Manual.md': 'Manual.md',
|
||||
'Exchange-Markets.md': 'Exchange-Markets.md',
|
||||
'Exchange-Markets-By-Country.md': 'Exchange-Markets-By-Country.md',
|
||||
'ccxt.pro.md': 'ccxt.pro.md',
|
||||
'ccxt.pro.install.md': 'ccxt.pro.install.md',
|
||||
'ccxt.pro.manual.md': 'ccxt.pro.manual.md',
|
||||
}
|
||||
|
||||
for (const [ sourceFile, destinationFile ] of entries (ccxtWikiFiles)) {
|
||||
|
||||
const sourcePath = wikiPath + '/' + sourceFile
|
||||
const destinationPath = gitWikiPath + '/' + destinationFile
|
||||
log.bright.cyan ('Exporting', sourcePath.yellow, '→', destinationPath.yellow)
|
||||
fs.writeFileSync (destinationPath, fs.readFileSync (sourcePath))
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function exportKeywordsToPackageJson (exchanges) {
|
||||
|
||||
log.bright ('Exporting exchange keywords to'.cyan, 'package.json'.yellow)
|
||||
|
||||
// const packageJSON = require ('../package.json')
|
||||
const packageJSON = JSON.parse (fs.readFileSync ('./package.json'))
|
||||
const keywords = new Set (packageJSON.keywords)
|
||||
|
||||
for (const ex of values (exchanges)) {
|
||||
for (const url of Array.isArray (ex.urls.www) ? ex.urls.www : [ex.urls.www]) {
|
||||
keywords.add (url.replace (/(http|https):\/\/(www\.)?/, '').replace (/\/.*/, ''))
|
||||
}
|
||||
keywords.add (ex.name)
|
||||
}
|
||||
|
||||
packageJSON.keywords = [...keywords]
|
||||
fs.writeFileSync ('./package.json', JSON.stringify (packageJSON, null, 2) + "\n")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function flatten (nested, result = []) {
|
||||
for (const key in nested) {
|
||||
result.push (key)
|
||||
if (Object.keys (nested[key]).length)
|
||||
flatten (nested[key], result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function exportEverything () {
|
||||
const ids = getIncludedExchangeIds ()
|
||||
const errorHierarchy = require ('../js/base/errorHierarchy.js')
|
||||
const flat = flatten (errorHierarchy)
|
||||
flat.push ('error_hierarchy')
|
||||
|
||||
const replacements = [
|
||||
{
|
||||
file: './ccxt.js',
|
||||
regex: /(?:const|var)\s+exchanges\s+\=\s+\{[^\}]+\}/,
|
||||
replacement: "const exchanges = {\n" + ids.map (id => (" '" + id + "':").padEnd (30) + " require ('./js/" + id + ".js'),").join ("\n") + " \n}",
|
||||
},
|
||||
{
|
||||
file: './python/ccxt/__init__.py',
|
||||
regex: /exchanges \= \[[^\]]+\]/,
|
||||
replacement: "exchanges = [\n" + " '" + ids.join ("',\n '") + "'," + "\n]",
|
||||
},
|
||||
{
|
||||
file: './python/ccxt/__init__.py',
|
||||
regex: /(?:from ccxt\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
|
||||
replacement: ids.map (id => ('from ccxt.' + id + ' import ' + id).padEnd (60) + '# noqa: F401').join ("\n") + "\n\nexchanges",
|
||||
},
|
||||
{
|
||||
file: './python/ccxt/__init__.py',
|
||||
regex: /(?:from ccxt\.base\.errors import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]/,
|
||||
replacement: flat.map (error => ('from ccxt.base.errors' + ' import ' + error).padEnd (60) + '# noqa: F401').join ("\n") + "\n\n",
|
||||
},
|
||||
{
|
||||
file: './python/ccxt/async_support/__init__.py',
|
||||
regex: /(?:from ccxt\.base\.errors import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]/,
|
||||
replacement: flat.map (error => ('from ccxt.base.errors' + ' import ' + error).padEnd (60) + '# noqa: F401').join ("\n") + "\n\n",
|
||||
},
|
||||
{
|
||||
file: './python/ccxt/async_support/__init__.py',
|
||||
regex: /(?:from ccxt\.async_support\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
|
||||
replacement: ids.map (id => ('from ccxt.async_support.' + id + ' import ' + id).padEnd (74) + '# noqa: F401').join ("\n") + "\n\nexchanges",
|
||||
},
|
||||
{
|
||||
file: './python/ccxt/async_support/__init__.py',
|
||||
regex: /exchanges \= \[[^\]]+\]/,
|
||||
replacement: "exchanges = [\n" + " '" + ids.join ("',\n '") + "'," + "\n]",
|
||||
},
|
||||
{
|
||||
file: './php/base/Exchange.php',
|
||||
regex: /public static \$exchanges \= array\s*\([^\)]+\)/,
|
||||
replacement: "public static $exchanges = array(\n '" + ids.join ("',\n '") + "',\n )",
|
||||
},
|
||||
]
|
||||
|
||||
exportExchanges (replacements)
|
||||
|
||||
// strategically placed exactly here (we can require it AFTER the export)
|
||||
const exchanges = createExchanges (ids)
|
||||
|
||||
const wikiPath = 'wiki'
|
||||
, gitWikiPath = 'build/ccxt.wiki'
|
||||
|
||||
cloneGitHubWiki (gitWikiPath)
|
||||
|
||||
exportSupportedAndCertifiedExchanges (exchanges, {
|
||||
allExchangesPaths: [
|
||||
'README.md',
|
||||
wikiPath + '/Manual.md',
|
||||
wikiPath + '/Exchange-Markets.md'
|
||||
],
|
||||
certifiedExchangesPaths: [
|
||||
'README.md',
|
||||
],
|
||||
exchangesByCountriesPaths: [
|
||||
wikiPath + '/Exchange-Markets-By-Country.md'
|
||||
],
|
||||
proExchangesPaths: [
|
||||
wikiPath + '/ccxt.pro.manual.md',
|
||||
],
|
||||
})
|
||||
|
||||
exportExchangeIdsToExchangesJson (exchanges)
|
||||
exportWikiToGitHub (wikiPath, gitWikiPath)
|
||||
exportKeywordsToPackageJson (exchanges)
|
||||
|
||||
log.bright.green ('Exported successfully.')
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// main entry point
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
// if called directly like `node module`
|
||||
|
||||
exportEverything ()
|
||||
|
||||
} else {
|
||||
|
||||
// do nothing if required as a module
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
module.exports = {
|
||||
cloneGitHubWiki,
|
||||
getIncludedExchangeIds,
|
||||
exportExchanges,
|
||||
createExchanges,
|
||||
exportSupportedAndCertifiedExchanges,
|
||||
exportExchangeIdsToExchangesJson,
|
||||
exportWikiToGitHub,
|
||||
exportKeywordsToPackageJson,
|
||||
exportEverything,
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require ('fs')
|
||||
, path = require ('path')
|
||||
|
||||
function replaceInFile (filename, regex, replacement) {
|
||||
const contents = fs.readFileSync (filename, 'utf8')
|
||||
const newContents = contents.replace (regex, replacement)
|
||||
fs.truncateSync (filename)
|
||||
fs.writeFileSync (filename, newContents)
|
||||
}
|
||||
|
||||
function copyFile (oldName, newName) {
|
||||
const contents = fs.readFileSync (oldName, 'utf8')
|
||||
if (fs.existsSync (newName)) {
|
||||
fs.truncateSync (newName)
|
||||
}
|
||||
fs.writeFileSync (newName, contents)
|
||||
}
|
||||
|
||||
function overwriteFile (filename, contents) {
|
||||
// log.cyan ('Overwriting → ' + filename.yellow)
|
||||
fs.closeSync (fs.openSync (filename, 'a'));
|
||||
fs.truncateSync (filename)
|
||||
fs.writeFileSync (filename, contents)
|
||||
}
|
||||
|
||||
function createFolder (folder) {
|
||||
try {
|
||||
fs.mkdirSync (folder)
|
||||
} catch (err) {
|
||||
if (err.code !== 'EEXIST') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createFolderRecursively (folder) {
|
||||
const parts = folder.split (path.sep)
|
||||
for (let i = 1; i <= parts.length; i++) {
|
||||
createFolder (path.join.apply (null, parts.slice (0, i)))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
replaceInFile,
|
||||
copyFile,
|
||||
overwriteFile,
|
||||
createFolder,
|
||||
createFolderRecursively,
|
||||
}
|
||||
Generated
Vendored
+1487
File diff suppressed because it is too large.
Load diff
+96
@@ -0,0 +1,96 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Usage:
|
||||
//
|
||||
// npm run vss
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require ('fs')
|
||||
, log = require ('ololog')
|
||||
, ansi = require ('ansicolor').nice
|
||||
, { execSync } = require ('child_process')
|
||||
, { copyFile } = require ('./fs.js')
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
function incrementVersionPatchNumber (version) {
|
||||
|
||||
let [ major, minor, patch ] = version.split ('.')
|
||||
|
||||
// we don't increment it here anymore, because
|
||||
// npm version patch will be explicitly called before
|
||||
|
||||
// patch = (parseInt (patch) + 1).toString ()
|
||||
|
||||
version = [ major, minor, patch ].join ('.')
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
function vss (filename, template, version) {
|
||||
log.bright.cyan ('Single-sourcing version', version, './package.json → ' + filename.yellow)
|
||||
const content = fs.readFileSync (filename, 'utf8')
|
||||
const regexp = new RegExp (template.replace (/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') // escape string for use in regexp
|
||||
.replace ('\\{version\\}', '\\d+\\.\\d+\\.\\d+'), 'g')
|
||||
fs.truncateSync (filename)
|
||||
fs.writeFileSync (filename, content.replace (regexp, template.replace ('{version}', version)))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function vssEverything () {
|
||||
|
||||
let { version } = require ('../package.json')
|
||||
|
||||
log.bright ('Old version: '.dim, version)
|
||||
version = incrementVersionPatchNumber (version)
|
||||
log.bright ('New version: '.cyan, version)
|
||||
|
||||
vss ('./ccxt.js', "const version = '{version}'", version)
|
||||
vss ('./php/base/Exchange.php', "$version = '{version}'", version)
|
||||
vss ('./php/base/Exchange.php', "VERSION = '{version}'", version)
|
||||
vss ('./python/ccxt/__init__.py', "__version__ = '{version}'", version)
|
||||
vss ('./python/ccxt/base/exchange.py', "__version__ = '{version}'", version)
|
||||
vss ('./python/ccxt/async_support/__init__.py', "__version__ = '{version}'", version)
|
||||
vss ('./python/ccxt/async_support/base/exchange.py', "__version__ = '{version}'", version)
|
||||
|
||||
vss ('./README.md', "ccxt@{version}", version)
|
||||
vss ('./wiki/Install.md', "ccxt@{version}", version)
|
||||
|
||||
const pythonFiles = [
|
||||
'package.json',
|
||||
'LICENSE.txt',
|
||||
'keys.json',
|
||||
'README.md',
|
||||
]
|
||||
|
||||
pythonFiles.forEach ((fileName) => copyFile ('./' + fileName, './python/' + fileName))
|
||||
|
||||
log.bright.green ('Version single-sourced successfully.')
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// main entry point
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
// if called directly like `node module`
|
||||
|
||||
vssEverything ()
|
||||
|
||||
} else {
|
||||
|
||||
// do nothing if required as a module
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
module.exports = {
|
||||
incrementVersionPatchNumber,
|
||||
vss,
|
||||
vssEverything,
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/* A entry point for the browser bundle version. This gets compiled by:
|
||||
|
||||
browserify --debug ./ccxt.browser.js > ./dist/ccxt.browser.js
|
||||
*/
|
||||
|
||||
window.ccxt = require ('./ccxt')
|
||||
+546
@@ -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 */
|
||||
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 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.
|
||||
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./js/base/Exchange')
|
||||
, functions = require ('./js/base/functions')
|
||||
, errors = require ('./js/base/errors')
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// this is updated by vss.js when building
|
||||
|
||||
const version = '1.38.72'
|
||||
|
||||
Exchange.ccxtVersion = version
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const exchanges = {
|
||||
'acx': require ('./js/acx.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'),
|
||||
'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'),
|
||||
'braziliex': require ('./js/braziliex.js'),
|
||||
'btcalpha': require ('./js/btcalpha.js'),
|
||||
'btcbox': require ('./js/btcbox.js'),
|
||||
'btcmarkets': require ('./js/btcmarkets.js'),
|
||||
'btctradeua': require ('./js/btctradeua.js'),
|
||||
'btcturk': require ('./js/btcturk.js'),
|
||||
'buda': require ('./js/buda.js'),
|
||||
'bw': require ('./js/bw.js'),
|
||||
'bybit': require ('./js/bybit.js'),
|
||||
'bytetrade': require ('./js/bytetrade.js'),
|
||||
'cex': require ('./js/cex.js'),
|
||||
'chilebit': require ('./js/chilebit.js'),
|
||||
'coinbase': require ('./js/coinbase.js'),
|
||||
'coinbaseprime': require ('./js/coinbaseprime.js'),
|
||||
'coinbasepro': require ('./js/coinbasepro.js'),
|
||||
'coincheck': require ('./js/coincheck.js'),
|
||||
'coinegg': require ('./js/coinegg.js'),
|
||||
'coinex': require ('./js/coinex.js'),
|
||||
'coinfalcon': require ('./js/coinfalcon.js'),
|
||||
'coinfloor': require ('./js/coinfloor.js'),
|
||||
'coingi': require ('./js/coingi.js'),
|
||||
'coinmarketcap': require ('./js/coinmarketcap.js'),
|
||||
'coinmate': require ('./js/coinmate.js'),
|
||||
'coinone': require ('./js/coinone.js'),
|
||||
'coinspot': require ('./js/coinspot.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'),
|
||||
'fcoinjp': require ('./js/fcoinjp.js'),
|
||||
'flowbtc': require ('./js/flowbtc.js'),
|
||||
'foxbit': require ('./js/foxbit.js'),
|
||||
'ftx': require ('./js/ftx.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'),
|
||||
'idex': require ('./js/idex.js'),
|
||||
'independentreserve': require ('./js/independentreserve.js'),
|
||||
'indodax': require ('./js/indodax.js'),
|
||||
'itbit': require ('./js/itbit.js'),
|
||||
'kraken': require ('./js/kraken.js'),
|
||||
'kucoin': require ('./js/kucoin.js'),
|
||||
'kuna': require ('./js/kuna.js'),
|
||||
'lakebtc': require ('./js/lakebtc.js'),
|
||||
'latoken': require ('./js/latoken.js'),
|
||||
'lbank': require ('./js/lbank.js'),
|
||||
'liquid': require ('./js/liquid.js'),
|
||||
'livecoin': require ('./js/livecoin.js'),
|
||||
'luno': require ('./js/luno.js'),
|
||||
'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'),
|
||||
'surbitcoin': require ('./js/surbitcoin.js'),
|
||||
'therock': require ('./js/therock.js'),
|
||||
'tidebit': require ('./js/tidebit.js'),
|
||||
'tidex': require ('./js/tidex.js'),
|
||||
'timex': require ('./js/timex.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'),
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = Object.assign ({ version, Exchange, 'exchanges': Object.keys (exchanges) }, exchanges, functions, errors)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
Generated
Vendored
+152824
File diff suppressed because it is too large.
Load diff
+84
@@ -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"
|
||||
}
|
||||
}
|
||||
+486
@@ -0,0 +1,486 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { InsufficientFunds, OrderNotFound } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class acx extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'acx',
|
||||
'name': 'ACX',
|
||||
'countries': [ 'AU' ],
|
||||
'rateLimit': 1000,
|
||||
'version': 'v2',
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': true,
|
||||
'createOrder': true,
|
||||
'fetchBalance': true,
|
||||
'fetchMarkets': true,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTickers': true,
|
||||
'fetchTime': true,
|
||||
'fetchTrades': true,
|
||||
'withdraw': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': '1',
|
||||
'5m': '5',
|
||||
'15m': '15',
|
||||
'30m': '30',
|
||||
'1h': '60',
|
||||
'2h': '120',
|
||||
'4h': '240',
|
||||
'12h': '720',
|
||||
'1d': '1440',
|
||||
'3d': '4320',
|
||||
'1w': '10080',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/30247614-1fe61c74-9621-11e7-9e8c-f1a627afa279.jpg',
|
||||
'extension': '.json',
|
||||
'api': 'https://acx.io/api',
|
||||
'www': 'https://acx.io',
|
||||
'doc': 'https://acx.io/documents/api_v2',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'depth', // Get depth or specified market Both asks and bids are sorted from highest price to lowest.
|
||||
'k_with_pending_trades', // Get K data with pending trades, which are the trades not included in K data yet, because there's delay between trade generated and processed by K data generator
|
||||
'k', // Get OHLC(k line) of specific market
|
||||
'markets', // Get all available markets
|
||||
'order_book', // Get the order book of specified market
|
||||
'order_book/{market}',
|
||||
'tickers', // Get ticker of all markets
|
||||
'tickers/{market}', // Get ticker of specific market
|
||||
'timestamp', // Get server current time, in seconds since Unix epoch
|
||||
'trades', // Get recent trades on market, each trade is included only once Trades are sorted in reverse creation order.
|
||||
'trades/{market}',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'members/me', // Get your profile and accounts info
|
||||
'deposits', // Get your deposits history
|
||||
'deposit', // Get details of specific deposit
|
||||
'deposit_address', // Where to deposit The address field could be empty when a new address is generating (e.g. for bitcoin), you should try again later in that case.
|
||||
'orders', // Get your orders, results is paginated
|
||||
'order', // Get information of specified order
|
||||
'trades/my', // Get your executed trades Trades are sorted in reverse creation order.
|
||||
'withdraws', // Get your cryptocurrency withdraws
|
||||
'withdraw', // Get your cryptocurrency withdraw
|
||||
],
|
||||
'post': [
|
||||
'orders', // Create a Sell/Buy order
|
||||
'orders/multi', // Create multiple sell/buy orders
|
||||
'orders/clear', // Cancel all my orders
|
||||
'order/delete', // Cancel an order
|
||||
'withdraw', // Create a withdraw
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'maker': 0.2 / 100,
|
||||
'taker': 0.2 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'withdraw': {}, // There is only 1% fee on withdrawals to your bank account.
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'PLA': 'Plair',
|
||||
},
|
||||
'exceptions': {
|
||||
'2002': InsufficientFunds,
|
||||
'2003': OrderNotFound,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const markets = await this.publicGetMarkets (params);
|
||||
const result = [];
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const id = market['id'];
|
||||
const symbol = market['name'];
|
||||
let baseId = this.safeString (market, 'base_unit');
|
||||
let quoteId = this.safeString (market, 'quote_unit');
|
||||
if ((baseId === undefined) || (quoteId === undefined)) {
|
||||
const ids = symbol.split ('/');
|
||||
baseId = ids[0].toLowerCase ();
|
||||
quoteId = ids[1].toLowerCase ();
|
||||
}
|
||||
let base = baseId.toUpperCase ();
|
||||
let quote = quoteId.toUpperCase ();
|
||||
base = this.safeCurrencyCode (base);
|
||||
quote = this.safeCurrencyCode (quote);
|
||||
// todo: find out their undocumented precision and limits
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'precision': precision,
|
||||
'info': market,
|
||||
'active': undefined,
|
||||
'limits': this.limits,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetMembersMe (params);
|
||||
const balances = this.safeValue (response, 'accounts');
|
||||
const result = { 'info': balances };
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[i];
|
||||
const currencyId = this.safeString (balance, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (balance, 'balance');
|
||||
account['used'] = this.safeFloat (balance, 'locked');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit; // default = 300
|
||||
}
|
||||
const orderbook = await this.publicGetDepth (this.extend (request, params));
|
||||
const timestamp = this.safeTimestamp (orderbook, 'timestamp');
|
||||
return this.parseOrderBook (orderbook, timestamp);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (ticker, 'at');
|
||||
ticker = ticker['ticker'];
|
||||
let symbol = undefined;
|
||||
if (market) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': this.safeFloat (ticker, 'open'),
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'vol'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetTickers (params);
|
||||
const ids = Object.keys (response);
|
||||
const result = {};
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
let market = undefined;
|
||||
let symbol = id;
|
||||
if (id in this.markets_by_id) {
|
||||
market = this.markets_by_id[id];
|
||||
symbol = market['symbol'];
|
||||
} else {
|
||||
let base = id.slice (0, 3);
|
||||
let quote = id.slice (3, 6);
|
||||
base = base.toUpperCase ();
|
||||
quote = quote.toUpperCase ();
|
||||
base = this.safeCurrencyCode (base);
|
||||
quote = this.safeCurrencyCode (quote);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
result[symbol] = this.parseTicker (response[id], market);
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTickersMarket (this.extend (request, params));
|
||||
return this.parseTicker (response, market);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (trade, 'created_at'));
|
||||
const id = this.safeString (trade, 'tid');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': undefined,
|
||||
'order': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
'price': this.safeFloat (trade, 'price'),
|
||||
'amount': this.safeFloat (trade, 'volume'),
|
||||
'cost': this.safeFloat (trade, 'funds'),
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTrades (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined) {
|
||||
return [
|
||||
this.safeTimestamp (ohlcv, 0),
|
||||
this.safeFloat (ohlcv, 1),
|
||||
this.safeFloat (ohlcv, 2),
|
||||
this.safeFloat (ohlcv, 3),
|
||||
this.safeFloat (ohlcv, 4),
|
||||
this.safeFloat (ohlcv, 5),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
if (limit === undefined) {
|
||||
limit = 500; // default is 30
|
||||
}
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
'period': this.timeframes[timeframe],
|
||||
'limit': limit,
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['timestamp'] = parseInt (since / 1000);
|
||||
}
|
||||
const response = await this.publicGetK (this.extend (request, params));
|
||||
return this.parseOHLCVs (response, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'done': 'closed',
|
||||
'wait': 'open',
|
||||
'cancel': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
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');
|
||||
const side = this.safeString (order, 'side');
|
||||
const id = this.safeString (order, 'id');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'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'),
|
||||
'trades': undefined,
|
||||
'fee': undefined,
|
||||
'info': order,
|
||||
'cost': undefined,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': parseInt (id),
|
||||
};
|
||||
const response = await this.privateGetOrder (this.extend (request, params));
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'market': this.marketId (symbol),
|
||||
'side': side,
|
||||
'volume': amount.toString (),
|
||||
'ord_type': type,
|
||||
};
|
||||
if (type === 'limit') {
|
||||
request['price'] = price.toString ();
|
||||
}
|
||||
const response = await this.privatePostOrders (this.extend (request, params));
|
||||
const marketId = this.safeValue (response, 'market');
|
||||
const market = this.safeValue (this.markets_by_id, marketId);
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privatePostOrderDelete (this.extend (request, params));
|
||||
const order = this.parseOrder (response);
|
||||
const status = order['status'];
|
||||
if (status === 'closed' || status === 'canceled') {
|
||||
throw new OrderNotFound (this.id + ' ' + this.json (order));
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
async withdraw (code, amount, address, tag = undefined, params = {}) {
|
||||
this.checkAddress (address);
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
// they have XRP but no docs on memo/tag
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
'sum': amount,
|
||||
'address': address,
|
||||
};
|
||||
const response = await this.privatePostWithdraw (this.extend (request, params));
|
||||
// withdrawal response is undocumented
|
||||
return {
|
||||
'info': response,
|
||||
'id': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
encodeParams (params) {
|
||||
if ('orders' in params) {
|
||||
const orders = params['orders'];
|
||||
let query = this.urlencode (this.keysort (this.omit (params, 'orders')));
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
const order = orders[i];
|
||||
const keys = Object.keys (order);
|
||||
for (let k = 0; k < keys.length; k++) {
|
||||
const key = keys[k];
|
||||
const value = order[key];
|
||||
query += '&orders%5B%5D%5B' + key + '%5D=' + value.toString ();
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
return this.urlencode (this.keysort (params));
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let request = '/api/' + this.version + '/' + this.implodeParams (path, params);
|
||||
if ('extension' in this.urls) {
|
||||
request += this.urls['extension'];
|
||||
}
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
let url = this.urls['api'] + request;
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
const query = this.encodeParams (this.extend ({
|
||||
'access_key': this.apiKey,
|
||||
'tonce': nonce,
|
||||
}, params));
|
||||
const auth = method + '|' + request + '|' + query;
|
||||
const signed = this.hmac (this.encode (auth), this.encode (this.secret));
|
||||
const suffix = query + '&signature=' + signed;
|
||||
if (method === 'GET') {
|
||||
url += '?' + suffix;
|
||||
} else {
|
||||
body = suffix;
|
||||
headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
}
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return;
|
||||
}
|
||||
if (code === 400) {
|
||||
const error = this.safeValue (response, 'error');
|
||||
const errorCode = this.safeString (error, 'code');
|
||||
const feedback = this.id + ' ' + this.json (response);
|
||||
this.throwExactlyMatchedException (this.exceptions, errorCode, feedback);
|
||||
// fallback to default error handler
|
||||
}
|
||||
}
|
||||
};
|
||||
+1096
File diff suppressed because it is too large.
Load diff
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017,
|
||||
"sourceType": "script",
|
||||
},
|
||||
"rules": {
|
||||
"semi": "off",
|
||||
"no-unused-vars": ["off"],
|
||||
"quotes": ["off", "single"],
|
||||
"quote-props": "off",
|
||||
"func-call-spacing": ["error", "always"],
|
||||
"one-var": "off",
|
||||
"indent": ["warn", 4, { "ignoredNodes": ["ObjectPattern"] }],
|
||||
"comma-style": "off",
|
||||
"no-multi-spaces": "off",
|
||||
"comma-dangle": "off",
|
||||
"spaced-comment": "off",
|
||||
"camelcase": "off",
|
||||
"padded-blocks": "off",
|
||||
"multiline-comment-style": "off",
|
||||
"curly": "off",
|
||||
"rest-spread-spacing": "off",
|
||||
"no-multiple-empty-lines": "off",
|
||||
"no-undef-init": "off",
|
||||
"no-useless-return": "off",
|
||||
"no-console": "off",
|
||||
"operator-linebreak": "off",
|
||||
"key-spacing": "off",
|
||||
"brace-style": "off",
|
||||
"padding-line-between-statements": ["off",
|
||||
{"blankLine": "never", "prev":"*", "next": "*" },
|
||||
{"blankLine": "always", "prev":"directive", "next": "*" },
|
||||
{"blankLine": "always", "prev":"*", "next": "cjs-export" },
|
||||
],
|
||||
},
|
||||
}
|
||||
Generated
Vendored
+1400
File diff suppressed because it is too large.
Load diff
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
const errorHierarchy = {
|
||||
'BaseError': {
|
||||
'ExchangeError': {
|
||||
'AuthenticationError': {
|
||||
'PermissionDenied': {},
|
||||
'AccountSuspended': {},
|
||||
},
|
||||
'ArgumentsRequired': {},
|
||||
'BadRequest': {
|
||||
'BadSymbol': {},
|
||||
},
|
||||
'BadResponse': {
|
||||
'NullResponse': {},
|
||||
},
|
||||
'InsufficientFunds': {},
|
||||
'InvalidAddress': {
|
||||
'AddressPending': {},
|
||||
},
|
||||
'InvalidOrder': {
|
||||
'OrderNotFound': {},
|
||||
'OrderNotCached': {},
|
||||
'CancelPending': {},
|
||||
'OrderImmediatelyFillable': {},
|
||||
'OrderNotFillable': {},
|
||||
'DuplicateOrderId': {},
|
||||
},
|
||||
'NotSupported': {},
|
||||
},
|
||||
'NetworkError': {
|
||||
'DDoSProtection': {
|
||||
'RateLimitExceeded': {},
|
||||
},
|
||||
'ExchangeNotAvailable': {
|
||||
'OnMaintenance': {},
|
||||
},
|
||||
'InvalidNonce': {},
|
||||
'RequestTimeout': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = errorHierarchy;
|
||||
Generated
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
const errorHierarchy = require ('./errorHierarchy.js')
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
function subclass (BaseClass, classes, namespace = {}) {
|
||||
|
||||
for (const [className, subclasses] of Object.entries (classes)) {
|
||||
|
||||
const Class = Object.assign (namespace, {
|
||||
|
||||
/* By creating a named property, we trick compiler to assign our class constructor function a name.
|
||||
Otherwise, all our error constructors would be shown as [Function: Error] in the debugger! And
|
||||
the super-useful `e.constructor.name` magic wouldn't work — we then would have no chance to
|
||||
obtain a error type string from an error instance programmatically! */
|
||||
|
||||
[className]: class extends BaseClass {
|
||||
|
||||
constructor (message) {
|
||||
|
||||
super (message)
|
||||
|
||||
/* A workaround to make `instanceof` work on custom Error classes in transpiled ES5.
|
||||
See my blog post for the explanation of this hack:
|
||||
|
||||
https://medium.com/@xpl/javascript-deriving-from-error-properly-8d2f8f315801 */
|
||||
|
||||
this.constructor = Class
|
||||
this.__proto__ = Class.prototype
|
||||
this.name = className
|
||||
this.message = message
|
||||
}
|
||||
}
|
||||
|
||||
})[className]
|
||||
|
||||
subclass (Class, subclasses, namespace)
|
||||
}
|
||||
|
||||
return namespace
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
module.exports = subclass (
|
||||
// Root class
|
||||
Error,
|
||||
// Derived class hierarchy
|
||||
errorHierarchy
|
||||
)
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const { unCamelCase } = require ('./functions/string')
|
||||
|
||||
const unCamelCasePropertyNames = x => {
|
||||
for (const k in x) x[unCamelCase (k)] = x[k] // camel_case_method = camelCaseMethod
|
||||
return x
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
module.exports = unCamelCasePropertyNames (Object.assign ({}
|
||||
|
||||
, require ('./functions/platform')
|
||||
, require ('./functions/generic')
|
||||
, require ('./functions/string')
|
||||
, require ('./functions/type')
|
||||
, require ('./functions/number')
|
||||
, require ('./functions/encode')
|
||||
, require ('./functions/crypto')
|
||||
, require ('./functions/time')
|
||||
, require ('./functions/throttle')
|
||||
, require ('./functions/misc')
|
||||
))
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
'use strict'
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js')
|
||||
const { capitalize } = require ('./string')
|
||||
const { stringToBase64, urlencodeBase64 } = require ('./encode')
|
||||
const NodeRSA = require ('./../../static_dependencies/node-rsa/NodeRSA')
|
||||
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')
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const hash = (request, hash = 'md5', digest = 'hex') => {
|
||||
const options = {}
|
||||
if (hash === 'keccak') {
|
||||
hash = 'SHA3'
|
||||
options['outputLength'] = 256
|
||||
}
|
||||
const result = CryptoJS[hash.toUpperCase ()] (request, options)
|
||||
return (digest === 'binary') ? result : result.toString (CryptoJS.enc[capitalize (digest)])
|
||||
}
|
||||
|
||||
/* ............................................. */
|
||||
|
||||
const hmac = (request, secret, hash = 'sha256', digest = 'hex') => {
|
||||
const result = CryptoJS['Hmac' + hash.toUpperCase ()] (request, secret)
|
||||
if (digest) {
|
||||
const encoding = (digest === 'binary') ? 'Latin1' : capitalize (digest)
|
||||
return result.toString (CryptoJS.enc[capitalize (encoding)])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/* ............................................. */
|
||||
|
||||
function rsa (request, secret, alg = 'RS256') {
|
||||
const algos = {
|
||||
'RS256': 'pkcs1-sha256',
|
||||
'RS512': 'pkcs1-sha512',
|
||||
}
|
||||
if (!(alg in algos)) {
|
||||
throw new ExchangeError (alg + ' is not a supported rsa signing algorithm.')
|
||||
}
|
||||
const algorithm = algos[alg]
|
||||
let key = new NodeRSA (secret, {
|
||||
'environment': 'browser',
|
||||
'signingScheme': algorithm,
|
||||
})
|
||||
return key.sign (request, 'base64', 'binary')
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
function jwt (request, secret, alg = 'HS256') {
|
||||
const algos = {
|
||||
'HS256': 'sha256',
|
||||
'HS384': 'sha384',
|
||||
'HS512': 'sha512',
|
||||
};
|
||||
const encodedHeader = urlencodeBase64 (stringToBase64 (JSON.stringify ({ 'alg': alg, 'typ': 'JWT' })))
|
||||
const encodedData = urlencodeBase64 (stringToBase64 (JSON.stringify (request)))
|
||||
const token = [ encodedHeader, encodedData ].join ('.')
|
||||
const algoType = alg.slice (0, 2);
|
||||
const algorithm = algos[alg]
|
||||
let signature = undefined
|
||||
if (algoType === 'HS') {
|
||||
signature = urlencodeBase64 (hmac (token, secret, algorithm, 'base64'))
|
||||
} else if (algoType === 'RS') {
|
||||
signature = urlencodeBase64 (rsa (token, secret, alg))
|
||||
}
|
||||
return [ token, signature ].join ('.')
|
||||
}
|
||||
|
||||
function ecdsa (request, secret, algorithm = 'p256', hashFunction = undefined, fixedLength = false) {
|
||||
let digest = request
|
||||
if (hashFunction !== undefined) {
|
||||
digest = hash (request, hashFunction, 'hex')
|
||||
}
|
||||
const curve = new EC (algorithm)
|
||||
let signature = curve.sign (digest, secret, 'hex', { 'canonical': true })
|
||||
let counter = new BN ('0')
|
||||
const minimum_size = new BN ('1').shln (8 * 31).sub (new BN ('1'))
|
||||
while (fixedLength && (signature.r.gt (curve.nh) || signature.r.lte (minimum_size) || signature.s.lte (minimum_size))) {
|
||||
signature = curve.sign (digest, secret, 'hex', { 'canonical': true, 'extraEntropy': counter.toArray ('le', 32)})
|
||||
counter = counter.add (new BN ('1'))
|
||||
}
|
||||
return {
|
||||
'r': signature.r.toString (16).padStart (64, '0'),
|
||||
's': signature.s.toString (16).padStart (64, '0'),
|
||||
'v': signature.recoveryParam,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) => {
|
||||
|
||||
const dec2hex = s => ((s < 15.5 ? '0' : '') + Math.round (s).toString (16))
|
||||
, hex2dec = s => parseInt (s, 16)
|
||||
, leftpad = (s, p) => (p + s).slice (-p.length) // both s and p are short strings
|
||||
|
||||
const base32tohex = (base32) => {
|
||||
let base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
let bits = ''
|
||||
let hex = ''
|
||||
for (let i = 0; i < base32.length; i++) {
|
||||
let val = base32chars.indexOf (base32.charAt (i).toUpperCase ())
|
||||
bits += leftpad (val.toString (2), '00000')
|
||||
}
|
||||
for (let i = 0; i + 4 <= bits.length; i += 4) {
|
||||
let chunk = bits.substr (i, 4)
|
||||
hex = hex + parseInt (chunk, 2).toString (16)
|
||||
}
|
||||
return hex
|
||||
}
|
||||
|
||||
const getOTP = (secret) => {
|
||||
secret = secret.replace (' ', '') // support 2fa-secrets with spaces like "4TDV WOGO" → "4TDVWOGO"
|
||||
let epoch = Math.round (new Date ().getTime () / 1000.0)
|
||||
let time = leftpad (dec2hex (Math.floor (epoch / 30)), '0000000000000000')
|
||||
let hmacRes = hmac (CryptoJS.enc.Hex.parse (time), CryptoJS.enc.Hex.parse (base32tohex (secret)), 'sha1', 'hex')
|
||||
let offset = hex2dec (hmacRes.substring (hmacRes.length - 1))
|
||||
let otp = (hex2dec (hmacRes.substr (offset * 2, 8)) & hex2dec ('7fffffff')) + ''
|
||||
otp = (otp).substr (otp.length - 6, 6)
|
||||
return otp
|
||||
}
|
||||
|
||||
return getOTP (secret)
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
module.exports = {
|
||||
hash,
|
||||
hmac,
|
||||
jwt,
|
||||
totp,
|
||||
rsa,
|
||||
ecdsa,
|
||||
eddsa,
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
"use strict";
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
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 =
|
||||
|
||||
{ json: (data, params = undefined) => JSON.stringify (data)
|
||||
, unjson: JSON.parse
|
||||
|
||||
, isJsonEncodedObject: object => (
|
||||
(typeof object === 'string') &&
|
||||
(object.length >= 2) &&
|
||||
((object[0] === '{') || (object[0] === '['))
|
||||
)
|
||||
|
||||
, stringToBinary: string => CryptoJS.enc.Latin1.parse (string)
|
||||
, stringToBase64: string => CryptoJS.enc.Latin1.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)
|
||||
, base16ToBinary: string => CryptoJS.enc.Hex.parse (string)
|
||||
, binaryToBase16: binary => binary.toString (CryptoJS.enc.Hex)
|
||||
, binaryConcat: (...args) => args.reduce ((a, b) => a.concat (b))
|
||||
, binaryConcatArray: (arr) => arr.reduce ((a, b) => a.concat (b))
|
||||
|
||||
, urlencode: object => qs.stringify (object)
|
||||
, urlencodeWithArrayRepeat: object => qs.stringify (object, { arrayFormat: 'repeat' })
|
||||
, rawencode: object => qs.stringify (object, { encode: false })
|
||||
, encode: x => x
|
||||
, decode: x => x
|
||||
|
||||
// Url-safe-base64 without equals signs, with + replaced by - and slashes replaced by underscores
|
||||
|
||||
, urlencodeBase64: base64string => base64string.replace (/[=]+$/, '')
|
||||
.replace (/\+/g, '-')
|
||||
.replace (/\//g, '_')
|
||||
|
||||
, numberToLE: (n, padding) => {
|
||||
const hexArray = new BN (n).toArray ('le', padding)
|
||||
return byteArrayToWordArray (hexArray)
|
||||
}
|
||||
|
||||
, numberToBE: (n, padding) => {
|
||||
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) {
|
||||
const wa = []
|
||||
for (let i = 0; i < ba.length; i++) {
|
||||
wa[(i / 4) | 0] |= ba[i] << (24 - 8 * i)
|
||||
}
|
||||
return CryptoJS.lib.WordArray.create (wa, ba.length)
|
||||
}
|
||||
|
||||
module.exports['byteArrayToWordArray'] = byteArrayToWordArray
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+236
@@ -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
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
}
|
||||
Generated
Vendored
+138
@@ -0,0 +1,138 @@
|
||||
'use strict';
|
||||
|
||||
const { ROUND_UP, ROUND_DOWN } = require ('./number')
|
||||
const { NotSupported } = require ('../errors')
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// converts timeframe to seconds
|
||||
const parseTimeframe = (timeframe) => {
|
||||
|
||||
const amount = timeframe.slice (0, -1)
|
||||
const unit = timeframe.slice (-1)
|
||||
let scale = undefined;
|
||||
|
||||
if (unit === 'y') {
|
||||
scale = 60 * 60 * 24 * 365
|
||||
} else if (unit === 'M') {
|
||||
scale = 60 * 60 * 24 * 30
|
||||
} else if (unit === 'w') {
|
||||
scale = 60 * 60 * 24 * 7
|
||||
} else if (unit === 'd') {
|
||||
scale = 60 * 60 * 24
|
||||
} else if (unit === 'h') {
|
||||
scale = 60 * 60
|
||||
} else if (unit === 'm') {
|
||||
scale = 60
|
||||
} else if (unit === 's') {
|
||||
scale = 1
|
||||
} else {
|
||||
throw new NotSupported ('timeframe unit ' + unit + ' is not supported')
|
||||
}
|
||||
|
||||
return amount * scale
|
||||
}
|
||||
|
||||
const roundTimeframe = (timeframe, timestamp, direction = ROUND_DOWN) => {
|
||||
const ms = parseTimeframe (timeframe) * 1000
|
||||
// Get offset based on timeframe in milliseconds
|
||||
const offset = timestamp % ms
|
||||
return timestamp - offset + ((direction === ROUND_UP) ? ms : 0);
|
||||
}
|
||||
|
||||
// given a sorted arrays of trades (recent last) and a timeframe builds an array of OHLCV candles
|
||||
const buildOHLCVC = (trades, timeframe = '1m', since = -Infinity, limit = Infinity) => {
|
||||
const ms = parseTimeframe (timeframe) * 1000;
|
||||
const ohlcvs = [];
|
||||
const [ timestamp, /* open */, high, low, close, volume, count ] = [ 0, 1, 2, 3, 4, 5, 6 ];
|
||||
const oldest = Math.min (trades.length - 1, limit);
|
||||
|
||||
for (let i = 0; i <= oldest; i++) {
|
||||
const trade = trades[i];
|
||||
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;
|
||||
|
||||
if (candle === -1 || openingTime >= ohlcvs[candle][timestamp] + ms) {
|
||||
// moved to a new timeframe -> create a new candle from opening trade
|
||||
ohlcvs.push ([
|
||||
openingTime, // timestamp
|
||||
trade.price, // O
|
||||
trade.price, // H
|
||||
trade.price, // L
|
||||
trade.price, // C
|
||||
trade.amount, // V
|
||||
1, // count
|
||||
]);
|
||||
} else {
|
||||
// still processing the same timeframe -> update opening trade
|
||||
ohlcvs[candle][high] = Math.max (ohlcvs[candle][high], trade.price);
|
||||
ohlcvs[candle][low] = Math.min (ohlcvs[candle][low], trade.price);
|
||||
ohlcvs[candle][close] = trade.price;
|
||||
ohlcvs[candle][volume] += trade.amount;
|
||||
ohlcvs[candle][count]++;
|
||||
} // if
|
||||
} // for
|
||||
return ohlcvs;
|
||||
}
|
||||
|
||||
const extractParams = (string) => {
|
||||
const re = /{([\w-]+)}/g
|
||||
const matches = []
|
||||
let match = re.exec (string)
|
||||
while (match) {
|
||||
matches.push (match[1])
|
||||
match = re.exec (string)
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
const implodeParams = (string, params) => {
|
||||
if (!Array.isArray (params)) {
|
||||
const keys = Object.keys (params)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (!Array.isArray (params[key])) {
|
||||
string = string.replace ('{' + key + '}', params[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
function vwap (baseVolume, quoteVolume) {
|
||||
return ((baseVolume !== undefined) && (quoteVolume !== undefined) && (baseVolume > 0)) ? (quoteVolume / baseVolume) : undefined
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
module.exports = {
|
||||
|
||||
aggregate (bidasks) {
|
||||
|
||||
const result = {}
|
||||
|
||||
for (let i = 0; i < bidasks.length; i++) {
|
||||
const [ price, volume ] = bidasks[i];
|
||||
if (volume > 0) {
|
||||
result[price] = (result[price] || 0) + volume
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys (result).map ((price) => [parseFloat (price), parseFloat (result[price])])
|
||||
},
|
||||
|
||||
parseTimeframe,
|
||||
roundTimeframe,
|
||||
buildOHLCVC,
|
||||
ROUND_UP,
|
||||
ROUND_DOWN,
|
||||
|
||||
implodeParams,
|
||||
extractParams,
|
||||
|
||||
vwap,
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+339
@@ -0,0 +1,339 @@
|
||||
'use strict'
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
|
||||
NB: initially, I used objects for options passing:
|
||||
|
||||
decimalToPrecision ('123.456', { digits: 2, round: true, afterPoint: true })
|
||||
|
||||
...but it turns out it's hard to port that across different languages and it is also
|
||||
probably has a performance penalty -- while it's a performance critical code! So
|
||||
I switched to using named constants instead, as it is actually more readable and
|
||||
succinct, and surely doesn't come with any inherent performance downside:
|
||||
|
||||
decimalToPrecision ('123.456', ROUND, 2, DECIMAL_PLACES) */
|
||||
|
||||
const ROUND = 0 // rounding mode
|
||||
, TRUNCATE = 1
|
||||
, ROUND_UP = 2
|
||||
, ROUND_DOWN = 3
|
||||
|
||||
const DECIMAL_PLACES = 0 // digits counting mode
|
||||
, SIGNIFICANT_DIGITS = 1
|
||||
, TICK_SIZE = 2
|
||||
|
||||
const NO_PADDING = 0 // zero-padding mode
|
||||
, PAD_WITH_ZERO = 1
|
||||
|
||||
const precisionConstants = {
|
||||
ROUND,
|
||||
TRUNCATE,
|
||||
ROUND_UP,
|
||||
ROUND_DOWN,
|
||||
DECIMAL_PLACES,
|
||||
SIGNIFICANT_DIGITS,
|
||||
TICK_SIZE,
|
||||
NO_PADDING,
|
||||
PAD_WITH_ZERO,
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
// See https://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript for discussion
|
||||
|
||||
function numberToString (x) { // avoids scientific notation for too large and too small numbers
|
||||
|
||||
if (typeof x === 'string') return x
|
||||
|
||||
const s = x.toString ()
|
||||
if (Math.abs (x) < 1.0) {
|
||||
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 = (neg ? '-' : '') + '0.' + (new Array (e)).join ('0') + n.substring (neg)
|
||||
return x
|
||||
}
|
||||
} else {
|
||||
const parts = s.split ('e')
|
||||
if (parts[1]) {
|
||||
let e = parseInt (parts[1])
|
||||
const m = parts[0].split ('.')
|
||||
if (m[1]) {
|
||||
e -= m[1].length
|
||||
}
|
||||
return m[0] + m[1] + (new Array (e + 1)).join ('0')
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// expects non-scientific notation
|
||||
|
||||
const truncate_regExpCache = []
|
||||
, truncate_to_string = (num, precision = 0) => {
|
||||
num = numberToString (num)
|
||||
if (precision > 0) {
|
||||
const re = truncate_regExpCache[precision] || (truncate_regExpCache[precision] = new RegExp ("([-]*\\d+\\.\\d{" + precision + "})(\\d)"))
|
||||
const [ , result] = num.toString ().match (re) || [null, num]
|
||||
return result.toString ()
|
||||
}
|
||||
return parseInt (num).toString ()
|
||||
}
|
||||
, truncate = (num, precision = 0) => parseFloat (truncate_to_string (num, precision))
|
||||
|
||||
function precisionFromString (string) {
|
||||
const split = string.replace (/0+$/g, '').split ('.')
|
||||
return (split.length > 1) ? (split[1].length) : 0
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const decimalToPrecision = (x, roundingMode
|
||||
, numPrecisionDigits
|
||||
, countingMode = DECIMAL_PLACES
|
||||
, paddingMode = NO_PADDING) => {
|
||||
|
||||
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 ()
|
||||
}
|
||||
if (roundingMode === TRUNCATE) {
|
||||
return (x - (x % toNearest)).toString ()
|
||||
}
|
||||
}
|
||||
|
||||
/* handle tick size */
|
||||
if (countingMode === TICK_SIZE) {
|
||||
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
|
||||
missing = Number (decimalToPrecision (missing, ROUND, 8, DECIMAL_PLACES, NO_PADDING));
|
||||
const fpError = decimalToPrecision (missing / numPrecisionDigits, ROUND, Math.max (newNumPrecisionDigits, 8), DECIMAL_PLACES, NO_PADDING)
|
||||
if (precisionFromString (fpError) !== 0) {
|
||||
if (roundingMode === ROUND) {
|
||||
if (x > 0) {
|
||||
if (missing >= numPrecisionDigits / 2) {
|
||||
x = x - missing + numPrecisionDigits
|
||||
} else {
|
||||
x = x - missing
|
||||
}
|
||||
} else {
|
||||
if (missing >= numPrecisionDigits / 2) {
|
||||
x = Number (x) - missing
|
||||
} else {
|
||||
x = Number (x) - missing - numPrecisionDigits
|
||||
}
|
||||
}
|
||||
} else if (roundingMode === TRUNCATE) {
|
||||
x = x - missing
|
||||
}
|
||||
}
|
||||
return decimalToPrecision (x, ROUND, newNumPrecisionDigits, DECIMAL_PLACES, paddingMode);
|
||||
}
|
||||
|
||||
/* Convert to a string (if needed), skip leading minus sign (if any) */
|
||||
|
||||
const str = numberToString (x)
|
||||
, isNegative = str[0] === '-'
|
||||
, strStart = isNegative ? 1 : 0
|
||||
, strEnd = str.length
|
||||
|
||||
/* Find the dot position in the source buffer */
|
||||
|
||||
for (var strDot = 0; strDot < strEnd; strDot++)
|
||||
if (str[strDot] === '.')
|
||||
break
|
||||
|
||||
const hasDot = strDot < str.length
|
||||
|
||||
/* Char code constants */
|
||||
|
||||
const MINUS = 45
|
||||
, DOT = 46
|
||||
, ZERO = 48
|
||||
, ONE = (ZERO + 1)
|
||||
, FIVE = (ZERO + 5)
|
||||
, NINE = (ZERO + 9)
|
||||
|
||||
/* For -123.4567 the `chars` array will hold 01234567 (leading zero is reserved for rounding cases when 099 → 100) */
|
||||
|
||||
const chars = new Uint8Array ((strEnd - strStart) + (hasDot ? 0 : 1))
|
||||
chars[0] = ZERO
|
||||
|
||||
/* Validate & copy digits, determine certain locations in the resulting buffer */
|
||||
|
||||
let afterDot = chars.length
|
||||
, digitsStart = -1 // significant digits
|
||||
, digitsEnd = -1
|
||||
|
||||
for (var i = 1, j = strStart; j < strEnd; j++, i++) {
|
||||
|
||||
const c = str.charCodeAt (j)
|
||||
|
||||
if (c === DOT) {
|
||||
afterDot = i--
|
||||
|
||||
} else if ((c < ZERO) || (c > NINE)) {
|
||||
throw new Error (`${str}: invalid number (contains an illegal character '${str[i - 1]}')`)
|
||||
|
||||
} else {
|
||||
chars[i] = c
|
||||
if ((c !== ZERO) && (digitsStart < 0)) digitsStart = i
|
||||
}
|
||||
}
|
||||
|
||||
if (digitsStart < 0) digitsStart = 1
|
||||
|
||||
/* Determine the range to cut */
|
||||
|
||||
let precisionStart = (countingMode === DECIMAL_PLACES) ? afterDot // 0.(0)001234567
|
||||
: digitsStart // 0.00(1)234567
|
||||
, precisionEnd = precisionStart +
|
||||
numPrecisionDigits
|
||||
|
||||
/* Reset the last significant digit index, as it will change during the rounding/truncation. */
|
||||
|
||||
digitsEnd = -1
|
||||
|
||||
/* Perform rounding/truncation per digit, from digitsEnd to digitsStart, by using the following
|
||||
algorithm (rounding 999 → 1000, as an example):
|
||||
|
||||
step = i=3 i=2 i=1 i=0
|
||||
|
||||
chars = 0999 0999 0900 1000
|
||||
memo = ---0 --1- -1-- 0--- */
|
||||
|
||||
let allZeros = true;
|
||||
let signNeeded = isNegative;
|
||||
|
||||
for (let i = chars.length - 1, memo = 0; i >= 0; i--) {
|
||||
|
||||
let c = chars[i]
|
||||
|
||||
if (i !== 0) {
|
||||
c += memo
|
||||
|
||||
if (i >= (precisionStart + numPrecisionDigits)) {
|
||||
|
||||
const ceil = (roundingMode === ROUND) &&
|
||||
(c >= FIVE) &&
|
||||
!((c === FIVE) && memo) // prevents rounding of 1.45 to 2
|
||||
|
||||
c = ceil ? (NINE + 1) : ZERO
|
||||
}
|
||||
if (c > NINE) { c = ZERO; memo = 1; }
|
||||
else memo = 0
|
||||
|
||||
} else if (memo) c = ONE // leading extra digit (0900 → 1000)
|
||||
|
||||
chars[i] = c
|
||||
|
||||
if (c !== ZERO) {
|
||||
allZeros = false
|
||||
digitsStart = i
|
||||
digitsEnd = (digitsEnd < 0) ? (i + 1) : digitsEnd
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the precision range, as `digitsStart` may have changed... & the need for a negative sign if it is only 0 */
|
||||
|
||||
if (countingMode === SIGNIFICANT_DIGITS) {
|
||||
precisionStart = digitsStart
|
||||
precisionEnd = precisionStart + numPrecisionDigits
|
||||
}
|
||||
|
||||
if (allZeros) {
|
||||
signNeeded = false
|
||||
}
|
||||
|
||||
/* Determine the input character range */
|
||||
|
||||
const readStart = ((digitsStart >= afterDot) || allZeros) ? (afterDot - 1) : digitsStart // 0.000(1)234 ----> (0).0001234
|
||||
, readEnd = (digitsEnd < afterDot) ? (afterDot ) : digitsEnd // 12(3)000 ----> 123000( )
|
||||
|
||||
/* Compute various sub-ranges */
|
||||
|
||||
const nSign = (signNeeded ? 1 : 0) // (-)123.456
|
||||
, nBeforeDot = (nSign + (afterDot - readStart)) // (-123).456
|
||||
, nAfterDot = Math.max (readEnd - afterDot, 0) // -123.(456)
|
||||
, actualLength = (readEnd - readStart) // -(123.456)
|
||||
, desiredLength = (paddingMode === NO_PADDING)
|
||||
? (actualLength) // -(123.456)
|
||||
: (precisionEnd - readStart) // -(123.456 )
|
||||
|
||||
, pad = Math.max (desiredLength - actualLength, 0) // -123.456( )
|
||||
, padStart = (nBeforeDot + 1 + nAfterDot) // -123.456( )
|
||||
, padEnd = (padStart + pad) // -123.456 ( )
|
||||
, isInteger = (nAfterDot + pad) === 0 // -123
|
||||
|
||||
/* Fill the output buffer with characters */
|
||||
|
||||
const out = new Uint8Array (nBeforeDot + (isInteger ? 0 : 1) + nAfterDot + pad)
|
||||
// ------------------------------------------------------------------------------------------ // ---------------------
|
||||
if (signNeeded) out[0] = MINUS // - minus sign
|
||||
for (i = nSign, j = readStart; i < nBeforeDot; i++, j++) out[i] = chars[j] // 123 before dot
|
||||
if (!isInteger) out[nBeforeDot] = DOT // . dot
|
||||
for (i = nBeforeDot + 1, j = afterDot; i < padStart; i++, j++) out[i] = chars[j] // 456 after dot
|
||||
for (i = padStart; i < padEnd; i++) out[i] = ZERO // 000 padding
|
||||
|
||||
/* Build a string from the output buffer */
|
||||
|
||||
return String.fromCharCode (...out)
|
||||
}
|
||||
|
||||
// toWei / fromWei
|
||||
|
||||
function fromWei (amount, decimals = 18) {
|
||||
if (amount === undefined) {
|
||||
return amount
|
||||
}
|
||||
const exponential = Math.floor (amount).toExponential () // wei must be whole numbers
|
||||
const [ n, exponent ] = exponential.split ('e')
|
||||
const newExponent = parseInt (exponent) - decimals
|
||||
return parseFloat (n + 'e' + newExponent)
|
||||
}
|
||||
|
||||
function toWei (amount, decimals = 18) {
|
||||
if (amount === undefined) {
|
||||
return amount
|
||||
}
|
||||
const exponential = parseFloat (amount).toExponential ()
|
||||
const [ n, exponent ] = exponential.split ('e')
|
||||
const newExponent = parseInt (exponent) + decimals
|
||||
return numberToString (Math.floor (parseFloat (n + 'e' + newExponent))) // wei must be whole numbers
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
module.exports = {
|
||||
toWei,
|
||||
fromWei,
|
||||
numberToString,
|
||||
precisionFromString,
|
||||
decimalToPrecision,
|
||||
truncate_to_string,
|
||||
truncate,
|
||||
precisionConstants,
|
||||
ROUND,
|
||||
TRUNCATE,
|
||||
ROUND_UP,
|
||||
ROUND_DOWN,
|
||||
DECIMAL_PLACES,
|
||||
SIGNIFICANT_DIGITS,
|
||||
TICK_SIZE,
|
||||
NO_PADDING,
|
||||
PAD_WITH_ZERO,
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// There's been a lot of messing with this code...
|
||||
// The problem is to satisfy the following requirements:
|
||||
// - properly detect isNode == true on server side and isNode == false in the browser (on client side)
|
||||
// - make sure create-react-app, react-starter-kit and other react frameworks work
|
||||
// - make sure it does not break the browserified version (when linked into a html from a cdn)
|
||||
// - make sure it does not break the webpacking and babel-transpiled scripts
|
||||
// - make sure it works in Electron
|
||||
// - make sure it works with Angular.js
|
||||
// - make sure it does not break other possible usage scenarios
|
||||
|
||||
const isBrowser = typeof window !== 'undefined'
|
||||
|
||||
const isElectron = typeof process !== 'undefined' &&
|
||||
typeof process.versions !== 'undefined' &&
|
||||
typeof process.versions.electron !== 'undefined'
|
||||
|
||||
const isWebWorker = typeof WorkerGlobalScope !== 'undefined' && (self instanceof WorkerGlobalScope)
|
||||
|
||||
const isWindows = typeof process !== 'undefined' && process.platform === "win32"
|
||||
|
||||
const isNode = !(isBrowser || isWebWorker)
|
||||
|
||||
const defaultFetch = typeof (fetch) === "undefined" ? require ('../../static_dependencies/fetch-ponyfill/fetch-node') ().fetch : fetch
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
|
||||
isBrowser,
|
||||
isElectron,
|
||||
isWebWorker,
|
||||
isNode,
|
||||
isWindows,
|
||||
defaultFetch,
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const uuid = a => a ? (a ^ Math.random () * 16 >> a / 4).toString (16)
|
||||
: ([1e7]+-1e3+-4e3+-8e3+-1e11).replace (/[018]/g, uuid)
|
||||
|
||||
module.exports =
|
||||
|
||||
{ uuid
|
||||
|
||||
// hasFetchOHLCV → has_fetch_ohlcv; parseHTTPResponse → parse_http_response
|
||||
, unCamelCase: s => s.match (/^[A-Z0-9_]+$/) ? s : (s.replace (/[a-z0-9][A-Z]/g, x => x[0] + '_' + x[1]).replace(/[A-Z0-9][A-Z0-9][a-z]/g, x => x[0] + '_' + x[1] + x[2]).toLowerCase ())
|
||||
|
||||
, capitalize: s => s.length
|
||||
? (s.charAt (0).toUpperCase () + s.slice (1))
|
||||
: s
|
||||
, strip: s => s.replace(/^\s+|\s+$/g, '')
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const { sleep
|
||||
, now } = require ('./time')
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
module.exports = {
|
||||
|
||||
throttle: function throttle (cfg) {
|
||||
|
||||
let lastTimestamp = now ()
|
||||
, numTokens = (cfg.numTokens !== undefined) ? cfg.numTokens : cfg.capacity
|
||||
, running = false
|
||||
, counter = 0
|
||||
|
||||
const queue = []
|
||||
|
||||
return Object.assign ((rateLimit, cost) => {
|
||||
|
||||
if (queue.length > cfg.maxCapacity)
|
||||
throw new Error ('Backlog is over max capacity of ' + cfg.maxCapacity)
|
||||
|
||||
return new Promise (async (resolve, reject) => {
|
||||
|
||||
try {
|
||||
queue.push ({ cost, resolve, reject })
|
||||
|
||||
if (!running) {
|
||||
running = true
|
||||
while (queue.length > 0) {
|
||||
const hasEnoughTokens = cfg.capacity ? (numTokens > 0) : (numTokens >= 0)
|
||||
if (hasEnoughTokens) {
|
||||
if (queue.length > 0) {
|
||||
let { cost, resolve, reject } = queue[0]
|
||||
cost = (cost || cfg.defaultCost)
|
||||
if (numTokens >= Math.min (cost, cfg.capacity)) {
|
||||
numTokens -= cost
|
||||
queue.shift ()
|
||||
resolve ()
|
||||
}
|
||||
}
|
||||
}
|
||||
const t = now ()
|
||||
, elapsed = t - lastTimestamp
|
||||
lastTimestamp = t
|
||||
numTokens = Math.min (cfg.capacity, numTokens + elapsed * (1 / rateLimit))
|
||||
await sleep (cfg.delay)
|
||||
}
|
||||
running = false
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
reject (e)
|
||||
}
|
||||
})
|
||||
|
||||
}, cfg, { configure: newCfg => throttle (Object.assign ({}, cfg, newCfg)) })
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
'use strict';
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const now = Date.now // TODO: figure out how to utilize performance.now () properly – it's not as easy as it does not return a unix timestamp...
|
||||
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
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const setTimeout_original = setTimeout
|
||||
const setTimeout_safe = (done, ms, setTimeout = setTimeout_original /* overrideable for mocking purposes */, targetTime = now () + ms) => {
|
||||
|
||||
/* The built-in setTimeout function can fire its callback earlier than specified, so we
|
||||
need to ensure that it does not happen: sleep recursively until `targetTime` is reached... */
|
||||
|
||||
let clearInnerTimeout = () => {}
|
||||
let active = true
|
||||
|
||||
const id = setTimeout (() => {
|
||||
active = true
|
||||
const rest = targetTime - now ()
|
||||
if (rest > 0) {
|
||||
clearInnerTimeout = setTimeout_safe (done, rest, setTimeout, targetTime) // try sleep more
|
||||
} else {
|
||||
done ()
|
||||
}
|
||||
}, ms)
|
||||
|
||||
return function clear () {
|
||||
if (active) {
|
||||
active = false // dunno if IDs are unique on various platforms, so it's better to rely on this flag to exclude the possible cancellation of the wrong timer (if called after completion)
|
||||
clearTimeout (id)
|
||||
}
|
||||
clearInnerTimeout ()
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
class TimedOut extends Error {
|
||||
|
||||
constructor () {
|
||||
const message = 'timed out'
|
||||
super (message)
|
||||
this.constructor = TimedOut
|
||||
this.__proto__ = TimedOut.prototype
|
||||
this.message = message
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const iso8601 = (timestamp) => {
|
||||
let _timestampNumber = undefined;
|
||||
if (typeof timestamp === 'number') {
|
||||
_timestampNumber = Math.floor (timestamp);
|
||||
} else {
|
||||
_timestampNumber = parseInt (timestamp, 10);
|
||||
}
|
||||
|
||||
// undefined, null and lots of nasty non-numeric values yield NaN
|
||||
if (Number.isNaN (_timestampNumber) || _timestampNumber < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// last line of defence
|
||||
try {
|
||||
return new Date (_timestampNumber).toISOString ();
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const parse8601 = (x) => {
|
||||
if (typeof x !== 'string' || !x) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (x.match (/^[0-9]+$/)) {
|
||||
// a valid number in a string, not a date.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (x.indexOf ('-') < 0 || x.indexOf (':') < 0) { // no date can be without a dash and a colon
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// last line of defence
|
||||
try {
|
||||
const candidate = Date.parse (((x.indexOf ('+') >= 0) || (x.slice (-1) === 'Z')) ? x : (x + 'Z').replace (/\s(\d\d):/, 'T$1:'));
|
||||
if (Number.isNaN (candidate)) {
|
||||
return undefined;
|
||||
}
|
||||
return candidate;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const parseDate = (x) => {
|
||||
if (typeof x !== 'string' || !x) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (x.indexOf ('GMT') >= 0) {
|
||||
try {
|
||||
return Date.parse (x);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return parse8601 (x);
|
||||
}
|
||||
|
||||
const rfc2616 = (timestamp = undefined) => {
|
||||
return new Date (timestamp).toUTCString ();
|
||||
}
|
||||
|
||||
const mdy = (timestamp, infix = '-') => {
|
||||
infix = infix || ''
|
||||
const date = new Date (timestamp)
|
||||
const Y = date.getUTCFullYear ().toString ()
|
||||
let m = date.getUTCMonth () + 1
|
||||
let d = date.getUTCDate ()
|
||||
m = m < 10 ? ('0' + m) : m.toString ()
|
||||
d = d < 10 ? ('0' + d) : d.toString ()
|
||||
return m + infix + d + infix + Y
|
||||
}
|
||||
|
||||
const ymd = (timestamp, infix = '-') => {
|
||||
infix = infix || ''
|
||||
const date = new Date (timestamp)
|
||||
const Y = date.getUTCFullYear ().toString ()
|
||||
let m = date.getUTCMonth () + 1
|
||||
let d = date.getUTCDate ()
|
||||
m = m < 10 ? ('0' + m) : m.toString ()
|
||||
d = d < 10 ? ('0' + d) : d.toString ()
|
||||
return Y + infix + m + infix + d
|
||||
}
|
||||
|
||||
const ymdhms = (timestamp, infix = ' ') => {
|
||||
const date = new Date (timestamp)
|
||||
const Y = date.getUTCFullYear ()
|
||||
let m = date.getUTCMonth () + 1
|
||||
let d = date.getUTCDate ()
|
||||
let H = date.getUTCHours ()
|
||||
let M = date.getUTCMinutes ()
|
||||
let S = date.getUTCSeconds ()
|
||||
m = m < 10 ? ('0' + m) : m
|
||||
d = d < 10 ? ('0' + d) : d
|
||||
H = H < 10 ? ('0' + H) : H
|
||||
M = M < 10 ? ('0' + M) : M
|
||||
S = S < 10 ? ('0' + S) : S
|
||||
return Y + '-' + m + '-' + d + infix + H + ':' + M + ':' + S
|
||||
}
|
||||
|
||||
module.exports =
|
||||
|
||||
{
|
||||
now
|
||||
, microseconds
|
||||
, milliseconds
|
||||
, seconds
|
||||
, iso8601
|
||||
, parse8601
|
||||
, rfc2616
|
||||
, uuidv1
|
||||
, parseDate
|
||||
, mdy
|
||||
, ymd
|
||||
, ymdhms
|
||||
, setTimeout_safe
|
||||
, sleep: ms => new Promise (resolve => setTimeout_safe (resolve, ms))
|
||||
, TimedOut
|
||||
, timeout: async (ms, promise) => {
|
||||
|
||||
let clear = () => {}
|
||||
const expires = new Promise (resolve => (clear = setTimeout_safe (resolve, ms)))
|
||||
|
||||
try {
|
||||
return await Promise.race ([promise, expires.then (() => { throw new TimedOut () })])
|
||||
} finally {
|
||||
clear () // fixes https://github.com/ccxt/ccxt/issues/749
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
Generated
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const isNumber = Number.isFinite
|
||||
, isInteger = Number.isInteger
|
||||
, isArray = Array.isArray
|
||||
, hasProps = o => ((o !== undefined) && (o !== null))
|
||||
, isString = s => (typeof s === 'string')
|
||||
, isObject = o => ((o !== null) && (typeof o === 'object'))
|
||||
, 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))
|
||||
|
||||
/* ............................................. */
|
||||
|
||||
const prop = (o, k) => (isObject (o) ? o[k] : undefined)
|
||||
, prop2 = (o, k1, k2) => (!isObject (o) ? undefined : (((k1 in o) && (o[k1] !== null)) ? o[k1] : o[k2]))
|
||||
|
||||
/* ............................................. */
|
||||
|
||||
const asFloat = x => ((isNumber (x) || isString (x)) ? parseFloat (x) : NaN)
|
||||
, asInteger = x => ((isNumber (x) || isString (x)) ? parseInt (x, 10) : NaN)
|
||||
|
||||
/* ............................................. */
|
||||
|
||||
module.exports = {
|
||||
|
||||
isNumber
|
||||
, isInteger
|
||||
, isArray
|
||||
, isObject
|
||||
, isString
|
||||
, isStringCoercible
|
||||
, isDictionary
|
||||
|
||||
, hasProps
|
||||
, prop
|
||||
|
||||
, asFloat
|
||||
, asInteger
|
||||
|
||||
, safeFloat: (o, k, $default, n = asFloat (prop (o, k))) => (isNumber (n) ? n : $default)
|
||||
, safeInteger: (o, k, $default, n = asInteger (prop (o, k))) => (isNumber (n) ? n : $default)
|
||||
, safeIntegerProduct: (o, k, $factor, $default, n = asInteger (prop (o, k))) => (isNumber (n) ? parseInt (n * $factor) : $default)
|
||||
, safeTimestamp: (o, k, $default, n = asFloat (prop (o, k))) => (isNumber (n) ? parseInt (n * 1000) : $default)
|
||||
, safeValue: (o, k, $default, x = prop (o, k)) => (hasProps (x) ? x : $default)
|
||||
, safeString: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x) : $default)
|
||||
, safeStringLower: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x).toLowerCase () : $default)
|
||||
, safeStringUpper: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x).toUpperCase () : $default)
|
||||
|
||||
// not using safeFloats with an array argument as we're trying to save some cycles here
|
||||
// we're not using safeFloat3 either because those cases are too rare to deserve their own optimization
|
||||
|
||||
, safeFloat2: (o, k1, k2, $default, n = asFloat (prop2 (o, k1, k2))) => (isNumber (n) ? n : $default)
|
||||
, safeInteger2: (o, k1, k2, $default, n = asInteger (prop2 (o, k1, k2))) => (isNumber (n) ? n : $default)
|
||||
, safeIntegerProduct2: (o, k1, k2, $factor, $default, n = asInteger (prop2 (o, k1, k2))) => (isNumber (n) ? parseInt (n * $factor) : $default)
|
||||
, safeTimestamp2: (o, k1, k2, $default, n = asFloat (prop2 (o, k1, k2))) => (isNumber (n) ? parseInt (n * 1000) : $default)
|
||||
, safeValue2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (hasProps (x) ? x : $default)
|
||||
, safeString2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x) : $default)
|
||||
, safeStringLower2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x).toLowerCase () : $default)
|
||||
, safeStringUpper2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x).toUpperCase () : $default)
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+687
@@ -0,0 +1,687 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, OrderNotFound } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bcex extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bcex',
|
||||
'name': 'BCEX',
|
||||
'countries': [ 'CN', 'HK' ],
|
||||
'version': '1',
|
||||
'has': {
|
||||
'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,
|
||||
'fetchTradingLimits': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/51840849/77231516-851c6900-6bac-11ea-8fd6-ee5c23eddbd4.jpg',
|
||||
'api': 'https://www.bcex.top',
|
||||
'www': 'https://www.bcex.top',
|
||||
'doc': 'https://github.com/BCEX-TECHNOLOGY-LIMITED/API_Docs/wiki/Interface',
|
||||
'fees': 'https://bcex.udesk.cn/hc/articles/57085',
|
||||
'referral': 'https://www.bcex.top/register?invite_code=758978&lang=en',
|
||||
},
|
||||
'status': {
|
||||
'status': 'error',
|
||||
'updated': undefined,
|
||||
'eta': undefined,
|
||||
'url': undefined,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'Api_Market/getPriceList', // tickers
|
||||
'Api_Order/ticker', // last ohlcv candle (ticker)
|
||||
'Api_Order/depth', // orderbook
|
||||
'Api_Market/getCoinTrade', // ticker
|
||||
'Api_Order/marketOrder', // trades...
|
||||
],
|
||||
'post': [
|
||||
'Api_Market/getPriceList', // tickers
|
||||
'Api_Order/ticker', // last ohlcv candle (ticker)
|
||||
'Api_Order/depth', // orderbook
|
||||
'Api_Market/getCoinTrade', // ticker
|
||||
'Api_Order/marketOrder', // trades...
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'Api_Order/cancel',
|
||||
'Api_Order/coinTrust', // limit order
|
||||
'Api_Order/orderList', // open / all orders (my trades?)
|
||||
'Api_Order/orderInfo',
|
||||
'Api_Order/tradeList', // open / all orders
|
||||
'Api_Order/trustList', // ?
|
||||
'Api_User/userBalance',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'maker': 0.1 / 100,
|
||||
'taker': 0.2 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': false,
|
||||
'withdraw': {
|
||||
'ckusd': 0.0,
|
||||
'other': 0.05 / 100,
|
||||
},
|
||||
'deposit': {},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'该币不存在,非法操作': ExchangeError, // { code: 1, msg: "该币不存在,非法操作" } - returned when a required symbol parameter is missing in the request (also, maybe on other types of errors as well)
|
||||
'公钥不合法': AuthenticationError, // { code: 1, msg: '公钥不合法' } - wrong public key
|
||||
'您的可用余额不足': InsufficientFunds, // { code: 1, msg: '您的可用余额不足' } - your available balance is insufficient
|
||||
'您的btc不足': InsufficientFunds, // { code: 1, msg: '您的btc不足' } - your btc is insufficient
|
||||
'参数非法': 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
|
||||
'AFC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 6, 'max': 120000 }}},
|
||||
'AFC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 6, 'max': 120000 }}},
|
||||
'AFT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 300000 }}},
|
||||
'AICC/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 5, 'max': 50000 }}},
|
||||
'AIDOC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 100000 }}},
|
||||
'AISI/ETH': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.001, 'max': 500 }}},
|
||||
'AIT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 400000 }}},
|
||||
'ANS/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 500 }}},
|
||||
'ANS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.1, 'max': 1000 }}},
|
||||
'ARC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 60, 'max': 600000 }}},
|
||||
'AXF/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 100, 'max': 1000000 }}},
|
||||
'BASH/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 250, 'max': 3000000 }}},
|
||||
'BATT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 60, 'max': 1500000 }}},
|
||||
'BCD/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.3, 'max': 7000 }}},
|
||||
'BHPC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 70000 }}},
|
||||
'BHPC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 60000 }}},
|
||||
'BOPO/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 2000000 }}},
|
||||
'BOPO/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 100, 'max': 10000000 }}},
|
||||
'BTC/CKUSD': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.001, 'max': 10 }}},
|
||||
'BTC/CNET': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.0005, 'max': 5 }}},
|
||||
'BTC/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.0002, 'max': 4 }}},
|
||||
'BTE/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 25, 'max': 250000 }}},
|
||||
'BU/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 400000 }}},
|
||||
'CIC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 3000, 'max': 30000000 }}},
|
||||
'CIT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 4, 'max': 40000 }}},
|
||||
'CIT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 4, 'max': 40000 }}},
|
||||
'CMT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 2500000 }}},
|
||||
'CNET/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 12, 'max': 120000 }}},
|
||||
'CNMC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 4, 'max': 50000 }}},
|
||||
'CTC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 5, 'max': 550000 }}},
|
||||
'CZR/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 12, 'max': 500000 }}},
|
||||
'DCON/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 8, 'max': 300000 }}},
|
||||
'DCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 40000 }}},
|
||||
'DCT/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 2, 'max': 2000 }}},
|
||||
'DOGE/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 3000, 'max': 14000000 }}},
|
||||
'DOGE/CKUSD': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 500, 'max': 2000000 }}},
|
||||
'DRCT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 16, 'max': 190000 }}},
|
||||
'ELA/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.02, 'max': 500 }}},
|
||||
'ELF/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 100000 }}},
|
||||
'ELF/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.01, 'max': 100000 }}},
|
||||
'EOS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.5, 'max': 5000 }}},
|
||||
'EOS/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 2.5, 'max': 30000 }}},
|
||||
'EOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.18, 'max': 1800 }}},
|
||||
'ETC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2500 }}},
|
||||
'ETC/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2500 }}},
|
||||
'ETF/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 7, 'max': 150000 }}},
|
||||
'ETH/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.015, 'max': 100 }}},
|
||||
'ETH/CKUSD': { 'precision': { 'amount': 4, 'price': 4 }, 'limits': { 'amount': { 'min': 0.005, 'max': 100 }}},
|
||||
'ETH/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.005, 'max': 100 }}},
|
||||
'FCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.24, 'max': 1000 }}},
|
||||
'FCT/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.24, 'max': 1000 }}},
|
||||
'GAME/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 1, 'max': 10000 }}},
|
||||
'GOOC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 200, 'max': 2000000 }}},
|
||||
'GP/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 600, 'max': 6000000 }}},
|
||||
'HSC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 1000, 'max': 20000000 }}},
|
||||
'IFISH/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 300, 'max': 8000000 }}},
|
||||
'IIC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 50, 'max': 4000000 }}},
|
||||
'IMOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 300000 }}},
|
||||
'JC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 300, 'max': 3000000 }}},
|
||||
'LBTC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 3000 }}},
|
||||
'LEC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 500, 'max': 5000000 }}},
|
||||
'LKY/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 10, 'max': 70000 }}},
|
||||
'LKY/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}},
|
||||
'LMC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 25, 'max': 250000 }}},
|
||||
'LSK/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.3, 'max': 3000 }}},
|
||||
'LTC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.01, 'max': 500 }}},
|
||||
'LTC/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.01, 'max': 500 }}},
|
||||
'LTC/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.02, 'max': 450 }}},
|
||||
'MC/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 10000, 'max': 100000000 }}},
|
||||
'MCC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 30, 'max': 350000 }}},
|
||||
'MOC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 25, 'max': 600000 }}},
|
||||
'MRYC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 300, 'max': 3000000 }}},
|
||||
'MT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 200, 'max': 6000000 }}},
|
||||
'MXI/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 5000, 'max': 60000000 }}},
|
||||
'NAI/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}},
|
||||
'NAS/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.2, 'max': 15000 }}},
|
||||
'NAS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.5, 'max': 5000 }}},
|
||||
'NEWOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 65, 'max': 700000 }}},
|
||||
'NKN/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 3, 'max': 350000 }}},
|
||||
'NTK/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 30000 }}},
|
||||
'ONT/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2000 }}},
|
||||
'ONT/ETH': { 'precision': { 'amount': 3, 'price': 8 }, 'limits': { 'amount': { 'min': 0.01, 'max': 1000 }}},
|
||||
'PNT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 80, 'max': 800000 }}},
|
||||
'PST/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 5, 'max': 100000 }}},
|
||||
'PTT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 450, 'max': 10000000 }}},
|
||||
'QTUM/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.4, 'max': 2800 }}},
|
||||
'QTUM/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.1, 'max': 1000 }}},
|
||||
'RATING/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 500, 'max': 10000000 }}},
|
||||
'RHC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 1000, 'max': 10000000 }}},
|
||||
'SDA/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 500000 }}},
|
||||
'SDD/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}},
|
||||
'SHC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 250, 'max': 2500000 }}},
|
||||
'SHE/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 5000000 }}},
|
||||
'SMC/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 1000, 'max': 10000000 }}},
|
||||
'SOP/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 50, 'max': 1000000 }}},
|
||||
'TAC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 35, 'max': 800000 }}},
|
||||
'TIP/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 7, 'max': 200000 }}},
|
||||
'TKT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 40, 'max': 400000 }}},
|
||||
'TLC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 500, 'max': 10000000 }}},
|
||||
'TNC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 110000 }}},
|
||||
'TUB/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 200, 'max': 8000000 }}},
|
||||
'UC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 3000000 }}},
|
||||
'UDB/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 2000, 'max': 40000000 }}},
|
||||
'UIC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 150000 }}},
|
||||
'VAAC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 250000 }}},
|
||||
'VPN/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 200, 'max': 2000000 }}},
|
||||
'VSC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 30, 'max': 650000 }}},
|
||||
'WAVES/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.15, 'max': 1500 }}},
|
||||
'WDNA/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 250000 }}},
|
||||
'WIC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 3, 'max': 30000 }}},
|
||||
'XAS/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 2.5, 'max': 25000 }}},
|
||||
'XLM/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 300000 }}},
|
||||
'XLM/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 1, 'max': 300000 }}},
|
||||
'XLM/USDT': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 150000 }}},
|
||||
'XRP/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 24, 'max': 100000 }}},
|
||||
'XRP/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 5, 'max': 50000 }}},
|
||||
'YBCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 200000 }}},
|
||||
'YBCT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 10, 'max': 200000 }}},
|
||||
'YBY/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 25000, 'max': 250000000 }}},
|
||||
'ZEC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.02, 'max': 100 }}},
|
||||
'ZEC/CKUSD': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.02, 'max': 100 }}},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchTradingLimits (symbols = undefined, params = {}) {
|
||||
// this method should not be called directly, use loadTradingLimits () instead
|
||||
// by default it will try load withdrawal fees of all currencies (with separate requests, sequentially)
|
||||
// however if you define symbols = [ 'ETH/BTC', 'LTC/BTC' ] in args it will only load those
|
||||
await this.loadMarkets ();
|
||||
if (symbols === undefined) {
|
||||
symbols = this.symbols;
|
||||
}
|
||||
const result = {};
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
const symbol = symbols[i];
|
||||
result[symbol] = await this.fetchTradingLimitsById (this.marketId (symbol), params);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchTradingLimitsById (id, params = {}) {
|
||||
const request = {
|
||||
'symbol': id,
|
||||
};
|
||||
const response = await this.publicPostApiOrderTicker (this.extend (request, params));
|
||||
//
|
||||
// { code: 0,
|
||||
// msg: "获取牌价信息成功",
|
||||
// data: { high: 0.03721392,
|
||||
// low: 0.03335362,
|
||||
// buy: "0.03525757",
|
||||
// sell: "0.03531160",
|
||||
// last: 0.0352634,
|
||||
// vol: "184742.4176",
|
||||
// min_trade: "0.01500000",
|
||||
// max_trade: "100.00000000",
|
||||
// number_float: "4",
|
||||
// price_float: "8" } } }
|
||||
//
|
||||
return this.parseTradingLimits (this.safeValue (response, 'data', {}));
|
||||
}
|
||||
|
||||
parseTradingLimits (limits, symbol = undefined, params = {}) {
|
||||
//
|
||||
// { high: 0.03721392,
|
||||
// low: 0.03335362,
|
||||
// buy: "0.03525757",
|
||||
// sell: "0.03531160",
|
||||
// last: 0.0352634,
|
||||
// vol: "184742.4176",
|
||||
// min_trade: "0.01500000",
|
||||
// max_trade: "100.00000000",
|
||||
// number_float: "4",
|
||||
// price_float: "8" }
|
||||
//
|
||||
return {
|
||||
'info': limits,
|
||||
'precision': {
|
||||
'amount': this.safeInteger (limits, 'number_float'),
|
||||
'price': this.safeInteger (limits, 'price_float'),
|
||||
},
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': this.safeFloat (limits, 'min_trade'),
|
||||
'max': this.safeFloat (limits, 'max_trade'),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetApiMarketGetPriceList (params);
|
||||
const result = [];
|
||||
const keys = Object.keys (response);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const currentMarketId = keys[i];
|
||||
const currentMarkets = response[currentMarketId];
|
||||
for (let j = 0; j < currentMarkets.length; j++) {
|
||||
const market = currentMarkets[j];
|
||||
const baseId = this.safeString (market, 'coin_from');
|
||||
const quoteId = this.safeString (market, 'coin_to');
|
||||
let base = baseId.toUpperCase ();
|
||||
let quote = quoteId.toUpperCase ();
|
||||
base = this.safeCurrencyCode (base);
|
||||
quote = this.safeCurrencyCode (quote);
|
||||
const id = baseId + '2' + quoteId;
|
||||
const symbol = base + '/' + quote;
|
||||
const active = true;
|
||||
const defaults = this.safeValue (this.options['limits'], symbol, {});
|
||||
result.push (this.extend ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
// overrided by defaults from this.options['limits']
|
||||
'precision': {
|
||||
'amount': undefined,
|
||||
'price': undefined,
|
||||
},
|
||||
// overrided by defaults from this.options['limits']
|
||||
'limits': {
|
||||
'amount': { 'min': undefined, 'max': undefined },
|
||||
'price': { 'min': undefined, 'max': undefined },
|
||||
'cost': { 'min': undefined, 'max': undefined },
|
||||
},
|
||||
'info': market,
|
||||
}, defaults));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeTimestamp2 (trade, 'date', 'created');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
const amount = this.safeFloat2 (trade, 'number', 'amount');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
let side = this.safeString (trade, 'side');
|
||||
if (side === 'sale') {
|
||||
side = 'sell';
|
||||
}
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'order': orderId,
|
||||
'fee': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const market = this.market (symbol);
|
||||
const response = await this.publicPostApiOrderMarketOrder (this.extend (request, params));
|
||||
return this.parseTrades (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostApiUserUserBalance (params);
|
||||
const data = this.safeValue (response, 'data');
|
||||
let keys = Object.keys (data);
|
||||
const result = { };
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const amount = this.safeFloat (data, key);
|
||||
const parts = key.split ('_');
|
||||
const currencyId = parts[0];
|
||||
const lockOrOver = parts[1];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
if (!(code in result)) {
|
||||
result[code] = this.account ();
|
||||
}
|
||||
if (lockOrOver === 'lock') {
|
||||
result[code]['used'] = parseFloat (amount);
|
||||
} else {
|
||||
result[code]['free'] = parseFloat (amount);
|
||||
}
|
||||
}
|
||||
keys = Object.keys (result);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const total = this.sum (result[key]['used'], result[key]['free']);
|
||||
result[key]['total'] = total;
|
||||
}
|
||||
result['info'] = data;
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.markets[symbol];
|
||||
const request = {
|
||||
'part': market['quoteId'],
|
||||
'coin': market['baseId'],
|
||||
};
|
||||
const response = await this.publicPostApiMarketGetCoinTrade (this.extend (request, params));
|
||||
const timestamp = this.milliseconds ();
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (response, 'max'),
|
||||
'low': this.safeFloat (response, 'min'),
|
||||
'bid': this.safeFloat (response, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (response, 'sale'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': this.safeFloat (response, 'price'),
|
||||
'last': this.safeFloat (response, 'price'),
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': this.safeFloat (response, 'change_24h'),
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (response, 'volume_24h'),
|
||||
'quoteVolume': undefined,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const marketId = this.marketId (symbol);
|
||||
const request = {
|
||||
'symbol': marketId,
|
||||
};
|
||||
const response = await this.publicPostApiOrderDepth (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
const timestamp = this.safeTimestamp (data, 'date');
|
||||
return this.parseOrderBook (data, timestamp);
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const response = await this.privatePostApiOrderOrderList (this.extend (request, params));
|
||||
return this.parseTrades (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'0': 'open',
|
||||
'1': 'open', // partially filled
|
||||
'2': 'closed',
|
||||
'3': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOrder requires a `symbol` argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'trust_id': id,
|
||||
};
|
||||
const response = await this.privatePostApiOrderOrderInfo (this.extend (request, params));
|
||||
const order = this.safeValue (response, 'data');
|
||||
const timestamp = this.safeTimestamp (order, 'created');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
let side = this.safeString (order, 'flag');
|
||||
if (side === 'sale') {
|
||||
side = 'sell';
|
||||
}
|
||||
// Can't use parseOrder because the data format is different btw endpoint for fetchOrder and fetchOrders
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'price': this.safeFloat (order, 'price'),
|
||||
'cost': undefined,
|
||||
'average': this.safeFloat (order, 'avg_price'),
|
||||
'amount': this.safeFloat (order, 'number'),
|
||||
'filled': this.safeFloat (order, 'numberdeal'),
|
||||
'remaining': this.safeFloat (order, 'numberover'),
|
||||
'status': status,
|
||||
'fee': undefined,
|
||||
'clientOrderId': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const id = this.safeString (order, 'id');
|
||||
const timestamp = this.safeTimestamp (order, 'datetime');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const type = undefined;
|
||||
let side = this.safeString (order, 'type');
|
||||
if (side === 'sale') {
|
||||
side = 'sell';
|
||||
}
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const average = this.safeFloat (order, 'avg_price');
|
||||
const amount = this.safeFloat (order, 'amount');
|
||||
const remaining = this.safeFloat (order, 'amount_outstanding');
|
||||
const filled = amount - remaining;
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const cost = filled * price;
|
||||
const fee = undefined;
|
||||
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,
|
||||
'cost': cost,
|
||||
'average': average,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'fee': fee,
|
||||
'trades': undefined,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrdersByType (type, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'type': type,
|
||||
};
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['symbol'] = market['id'];
|
||||
}
|
||||
const response = await this.privatePostApiOrderTradeList (this.extend (request, params));
|
||||
if ('data' in response) {
|
||||
return this.parseOrders (response['data'], market, since, limit);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return this.fetchOrdersByType ('open', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const orders = await this.fetchOrders (symbol, since, limit, params);
|
||||
return this.filterBy (orders, 'status', 'closed');
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return this.fetchOrdersByType ('all', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'type': side,
|
||||
'price': this.priceToPrecision (symbol, price),
|
||||
'number': this.amountToPrecision (symbol, amount),
|
||||
};
|
||||
const response = await this.privatePostApiOrderCoinTrust (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const id = this.safeString (data, 'order_id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
if (symbol !== undefined) {
|
||||
request['symbol'] = this.marketId (symbol);
|
||||
}
|
||||
if (id !== undefined) {
|
||||
request['order_id'] = id;
|
||||
}
|
||||
return await this.privatePostApiOrderCancel (this.extend (request, params));
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
let payload = this.urlencode ({ 'api_key': this.apiKey });
|
||||
if (Object.keys (query).length) {
|
||||
payload += '&' + this.urlencode (this.keysort (query));
|
||||
}
|
||||
const auth = payload + '&secret_key=' + this.secret;
|
||||
const signature = this.hash (this.encode (auth));
|
||||
body = payload + '&sign=' + signature;
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return; // fallback to default error handler
|
||||
}
|
||||
const errorCode = this.safeValue (response, 'code');
|
||||
if (errorCode !== undefined) {
|
||||
if (errorCode !== 0) {
|
||||
//
|
||||
// { code: 1, msg: "该币不存在,非法操作" } - returned when a required symbol parameter is missing in the request (also, maybe on other types of errors as well)
|
||||
// { code: 1, msg: '公钥不合法' } - wrong public key
|
||||
// { code: 1, msg: '价格输入有误,请检查你的数值精度' } - 'The price input is incorrect, please check your numerical accuracy'
|
||||
// { code: 1, msg: '单笔最小交易数量不能小于0.00100000,请您重新挂单'} -
|
||||
// 'The minimum number of single transactions cannot be less than 0.00100000. Please re-post the order'
|
||||
//
|
||||
const message = this.safeString (response, 'msg');
|
||||
const feedback = this.id + ' ' + message;
|
||||
this.throwExactlyMatchedException (this.exceptions, message, feedback);
|
||||
if (message.indexOf ('请您重新挂单') >= 0) { // minimum limit
|
||||
throw new InvalidOrder (feedback);
|
||||
} else {
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const hitbtc = require ('./hitbtc');
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bequant extends hitbtc {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bequant',
|
||||
'name': 'Bequant',
|
||||
'countries': [ 'MT' ], // Malta
|
||||
'pro': true,
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/55248342-a75dfe00-525a-11e9-8aa2-05e9dca943c6.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.bequant.io',
|
||||
'private': 'https://api.bequant.io',
|
||||
},
|
||||
'www': 'https://bequant.io',
|
||||
'doc': [
|
||||
'https://api.bequant.io/',
|
||||
],
|
||||
'fees': [
|
||||
'https://bequant.io/fees-and-limits',
|
||||
],
|
||||
'referral': 'https://bequant.io',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+1049
File diff suppressed because it is too large.
Load diff
+1267
File diff suppressed because it is too large.
Load diff
+2627
File diff suppressed because it is too large.
Load diff
+46
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const binance = require ('./binance.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class binanceus extends binance {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'binanceus',
|
||||
'name': 'Binance US',
|
||||
'countries': [ 'US' ], // US
|
||||
'certified': false,
|
||||
'pro': true,
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/65177307-217b7c80-da5f-11e9-876e-0b748ba0a358.jpg',
|
||||
'api': {
|
||||
'web': 'https://www.binance.us',
|
||||
'wapi': 'https://api.binance.us/wapi/v3',
|
||||
'public': 'https://api.binance.us/api/v1',
|
||||
'private': 'https://api.binance.us/api/v3',
|
||||
'v3': 'https://api.binance.us/api/v3',
|
||||
'v1': 'https://api.binance.us/api/v1',
|
||||
},
|
||||
'www': 'https://www.binance.us',
|
||||
'referral': 'https://www.binance.us/?ref=35005074',
|
||||
'doc': 'https://github.com/binance-us/binance-official-api-docs',
|
||||
'fees': 'https://www.binance.us/en/fee/schedule',
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'percentage': true,
|
||||
'taker': 0.001, // 0.1% trading fee, zero fees for all trading pairs before November 1
|
||||
'maker': 0.001, // 0.1% trading fee, zero fees for all trading pairs before November 1
|
||||
},
|
||||
},
|
||||
'options': {
|
||||
'quoteOrderQty': false,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+458
@@ -0,0 +1,458 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ArgumentsRequired, ExchangeError, InvalidNonce, AuthenticationError, PermissionDenied } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bit2c extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bit2c',
|
||||
'name': 'Bit2C',
|
||||
'countries': [ 'IL' ], // Israel
|
||||
'rateLimit': 3000,
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': false,
|
||||
'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',
|
||||
'api': 'https://bit2c.co.il',
|
||||
'www': 'https://www.bit2c.co.il',
|
||||
'referral': 'https://bit2c.co.il/Aff/63bfed10-e359-420c-ab5a-ad368dab0baf',
|
||||
'doc': [
|
||||
'https://www.bit2c.co.il/home/api',
|
||||
'https://github.com/OferE/bit2c',
|
||||
],
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'Exchanges/{pair}/Ticker',
|
||||
'Exchanges/{pair}/orderbook',
|
||||
'Exchanges/{pair}/trades',
|
||||
'Exchanges/{pair}/lasttrades',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'Merchant/CreateCheckout',
|
||||
'Order/AddCoinFundsRequest',
|
||||
'Order/AddFund',
|
||||
'Order/AddOrder',
|
||||
'Order/AddOrderMarketPriceBuy',
|
||||
'Order/AddOrderMarketPriceSell',
|
||||
'Order/CancelOrder',
|
||||
'Order/AddCoinFundsRequest',
|
||||
'Order/AddStopOrder',
|
||||
'Payment/GetMyId',
|
||||
'Payment/Send',
|
||||
'Payment/Pay',
|
||||
],
|
||||
'get': [
|
||||
'Account/Balance',
|
||||
'Account/Balance/v2',
|
||||
'Order/MyOrders',
|
||||
'Order/GetById',
|
||||
'Order/AccountHistory',
|
||||
'Order/OrderHistory',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/NIS': { 'id': 'BtcNis', 'symbol': 'BTC/NIS', 'base': 'BTC', 'quote': 'NIS', 'baseId': 'Btc', 'quoteId': 'Nis' },
|
||||
'ETH/NIS': { 'id': 'EthNis', 'symbol': 'ETH/NIS', 'base': 'ETH', 'quote': 'NIS', 'baseId': 'Eth', 'quoteId': 'Nis' },
|
||||
'BCH/NIS': { 'id': 'BchabcNis', 'symbol': 'BCH/NIS', 'base': 'BCH', 'quote': 'NIS', 'baseId': 'Bchabc', 'quoteId': 'Nis' },
|
||||
'LTC/NIS': { 'id': 'LtcNis', 'symbol': 'LTC/NIS', 'base': 'LTC', 'quote': 'NIS', 'baseId': 'Ltc', 'quoteId': 'Nis' },
|
||||
'ETC/NIS': { 'id': 'EtcNis', 'symbol': 'ETC/NIS', 'base': 'ETC', 'quote': 'NIS', 'baseId': 'Etc', 'quoteId': 'Nis' },
|
||||
'BTG/NIS': { 'id': 'BtgNis', 'symbol': 'BTG/NIS', 'base': 'BTG', 'quote': 'NIS', 'baseId': 'Btg', 'quoteId': 'Nis' },
|
||||
'BSV/NIS': { 'id': 'BchsvNis', 'symbol': 'BSV/NIS', 'base': 'BSV', 'quote': 'NIS', 'baseId': 'Bchsv', 'quoteId': 'Nis' },
|
||||
'GRIN/NIS': { 'id': 'GrinNis', 'symbol': 'GRIN/NIS', 'base': 'GRIN', 'quote': 'NIS', 'baseId': 'Grin', 'quoteId': 'Nis' },
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.5 / 100,
|
||||
'taker': 0.5 / 100,
|
||||
},
|
||||
},
|
||||
'options': {
|
||||
'fetchTradesMethod': 'public_get_exchanges_pair_trades',
|
||||
},
|
||||
'exceptions': {
|
||||
'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." }
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const balance = await this.privateGetAccountBalanceV2 (params);
|
||||
//
|
||||
// {
|
||||
// "AVAILABLE_NIS": 0.0,
|
||||
// "NIS": 0.0,
|
||||
// "LOCKED_NIS": 0.0,
|
||||
// "AVAILABLE_BTC": 0.0,
|
||||
// "BTC": 0.0,
|
||||
// "LOCKED_BTC": 0.0,
|
||||
// "AVAILABLE_ETH": 0.0,
|
||||
// "ETH": 0.0,
|
||||
// "LOCKED_ETH": 0.0,
|
||||
// "AVAILABLE_BCHSV": 0.0,
|
||||
// "BCHSV": 0.0,
|
||||
// "LOCKED_BCHSV": 0.0,
|
||||
// "AVAILABLE_BCHABC": 0.0,
|
||||
// "BCHABC": 0.0,
|
||||
// "LOCKED_BCHABC": 0.0,
|
||||
// "AVAILABLE_LTC": 0.0,
|
||||
// "LTC": 0.0,
|
||||
// "LOCKED_LTC": 0.0,
|
||||
// "AVAILABLE_ETC": 0.0,
|
||||
// "ETC": 0.0,
|
||||
// "LOCKED_ETC": 0.0,
|
||||
// "AVAILABLE_BTG": 0.0,
|
||||
// "BTG": 0.0,
|
||||
// "LOCKED_BTG": 0.0,
|
||||
// "AVAILABLE_GRIN": 0.0,
|
||||
// "GRIN": 0.0,
|
||||
// "LOCKED_GRIN": 0.0,
|
||||
// "Fees": {
|
||||
// "BtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "EthNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "BchabcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "LtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "EtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "BtgNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "LtcBtc": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "BchsvNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 },
|
||||
// "GrinNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
const result = { 'info': balance };
|
||||
const codes = Object.keys (this.currencies);
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const account = this.account ();
|
||||
const currencyId = this.currencyId (code);
|
||||
const uppercase = currencyId.toUpperCase ();
|
||||
if (uppercase in balance) {
|
||||
account['free'] = this.safeFloat (balance, 'AVAILABLE_' + uppercase);
|
||||
account['total'] = this.safeFloat (balance, uppercase);
|
||||
}
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'pair': this.marketId (symbol),
|
||||
};
|
||||
const orderbook = await this.publicGetExchangesPairOrderbook (this.extend (request, params));
|
||||
return this.parseOrderBook (orderbook);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'pair': this.marketId (symbol),
|
||||
};
|
||||
const ticker = await this.publicGetExchangesPairTicker (this.extend (request, params));
|
||||
const timestamp = this.milliseconds ();
|
||||
const averagePrice = this.safeFloat (ticker, 'av');
|
||||
const baseVolume = this.safeFloat (ticker, 'a');
|
||||
let quoteVolume = undefined;
|
||||
if (baseVolume !== undefined && averagePrice !== undefined) {
|
||||
quoteVolume = baseVolume * averagePrice;
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'll');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': this.safeFloat (ticker, 'h'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'l'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': averagePrice,
|
||||
'baseVolume': baseVolume,
|
||||
'quoteVolume': quoteVolume,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const method = this.options['fetchTradesMethod'];
|
||||
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);
|
||||
}
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let method = 'privatePostOrderAddOrder';
|
||||
const request = {
|
||||
'Amount': amount,
|
||||
'Pair': this.marketId (symbol),
|
||||
};
|
||||
if (type === 'market') {
|
||||
method += 'MarketPrice' + this.capitalize (side);
|
||||
} else {
|
||||
request['Price'] = price;
|
||||
request['Total'] = amount * price;
|
||||
request['IsBid'] = (side === 'buy');
|
||||
}
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['NewOrder']['id'],
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
return await this.privatePostOrderCancelOrder (this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
};
|
||||
const response = await this.privateGetOrderMyOrders (this.extend (request, params));
|
||||
const orders = this.safeValue (response, market['id'], {});
|
||||
const asks = this.safeValue (orders, 'ask', []);
|
||||
const bids = this.safeValue (orders, 'bid', []);
|
||||
return this.parseOrders (this.arrayConcat (asks, bids), market, since, limit);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const timestamp = this.safeInteger (order, 'created');
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const amount = this.safeFloat (order, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
let side = this.safeValue (order, 'type');
|
||||
if (side === 0) {
|
||||
side = 'buy';
|
||||
} else if (side === 1) {
|
||||
side = 'sell';
|
||||
}
|
||||
const id = this.safeString (order, 'id');
|
||||
const status = this.safeString (order, 'status');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'amount': amount,
|
||||
'filled': undefined,
|
||||
'remaining': undefined,
|
||||
'cost': cost,
|
||||
'trades': undefined,
|
||||
'fee': undefined,
|
||||
'info': order,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
const request = {};
|
||||
if (limit !== undefined) {
|
||||
request['take'] = limit;
|
||||
}
|
||||
request['take'] = limit;
|
||||
if (since !== undefined) {
|
||||
request['toTime'] = this.ymd (this.milliseconds (), '.');
|
||||
request['fromTime'] = this.ymd (since, '.');
|
||||
}
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['pair'] = market['id'];
|
||||
}
|
||||
const response = await this.privateGetOrderOrderHistory (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
let timestamp = undefined;
|
||||
let id = undefined;
|
||||
let price = undefined;
|
||||
let amount = undefined;
|
||||
let orderId = undefined;
|
||||
let feeCost = undefined;
|
||||
let side = undefined;
|
||||
const reference = this.safeString (trade, 'reference');
|
||||
if (reference !== undefined) {
|
||||
timestamp = this.safeTimestamp (trade, 'ticks');
|
||||
price = this.safeFloat (trade, 'price');
|
||||
amount = this.safeFloat (trade, 'firstAmount');
|
||||
const reference_parts = reference.split ('|'); // reference contains 'pair|orderId|tradeId'
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (trade, 'pair');
|
||||
if (marketId in this.markets_by_id[marketId]) {
|
||||
market = this.markets_by_id[marketId];
|
||||
} else if (reference_parts[0] in this.markets_by_id) {
|
||||
market = this.markets_by_id[reference_parts[0]];
|
||||
}
|
||||
}
|
||||
orderId = reference_parts[1];
|
||||
id = reference_parts[2];
|
||||
side = this.safeInteger (trade, 'action');
|
||||
if (side === 0) {
|
||||
side = 'buy';
|
||||
} else if (side === 1) {
|
||||
side = 'sell';
|
||||
}
|
||||
feeCost = this.safeFloat (trade, 'feeAmount');
|
||||
} else {
|
||||
timestamp = this.safeTimestamp (trade, 'date');
|
||||
id = this.safeString (trade, 'tid');
|
||||
price = this.safeFloat (trade, 'price');
|
||||
amount = this.safeFloat (trade, 'amount');
|
||||
side = this.safeValue (trade, 'isBid');
|
||||
if (side !== undefined) {
|
||||
if (side) {
|
||||
side = 'buy';
|
||||
} else {
|
||||
side = 'sell';
|
||||
}
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': orderId,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': price * amount,
|
||||
'fee': {
|
||||
'cost': feeCost,
|
||||
'currency': 'NIS',
|
||||
'rate': undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.implodeParams (path, params);
|
||||
if (api === 'public') {
|
||||
url += '.json';
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
const query = this.extend ({
|
||||
'nonce': nonce,
|
||||
}, params);
|
||||
const auth = this.urlencode (query);
|
||||
if (method === 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + auth;
|
||||
}
|
||||
} else {
|
||||
body = auth;
|
||||
}
|
||||
const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512', 'base64');
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'key': this.apiKey,
|
||||
'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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+622
@@ -0,0 +1,622 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, InvalidNonce, InsufficientFunds, InvalidOrder, OrderNotFound, PermissionDenied } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitbank extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitbank',
|
||||
'name': 'bitbank',
|
||||
'countries': [ 'JP' ],
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'createOrder': true,
|
||||
'fetchBalance': true,
|
||||
'fetchDepositAddress': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTrades': true,
|
||||
'withdraw': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': '1min',
|
||||
'5m': '5min',
|
||||
'15m': '15min',
|
||||
'30m': '30min',
|
||||
'1h': '1hour',
|
||||
'4h': '4hour',
|
||||
'8h': '8hour',
|
||||
'12h': '12hour',
|
||||
'1d': '1day',
|
||||
'1w': '1week',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/37808081-b87f2d9c-2e59-11e8-894d-c1900b7584fe.jpg',
|
||||
'api': {
|
||||
'public': 'https://public.bitbank.cc',
|
||||
'private': 'https://api.bitbank.cc',
|
||||
},
|
||||
'www': 'https://bitbank.cc/',
|
||||
'doc': 'https://docs.bitbank.cc/',
|
||||
'fees': 'https://bitbank.cc/docs/fees/',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'{pair}/ticker',
|
||||
'{pair}/depth',
|
||||
'{pair}/transactions',
|
||||
'{pair}/transactions/{yyyymmdd}',
|
||||
'{pair}/candlestick/{candletype}/{yyyymmdd}',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'user/assets',
|
||||
'user/spot/order',
|
||||
'user/spot/active_orders',
|
||||
'user/spot/trade_history',
|
||||
'user/withdrawal_account',
|
||||
],
|
||||
'post': [
|
||||
'user/spot/order',
|
||||
'user/spot/cancel_order',
|
||||
'user/spot/cancel_orders',
|
||||
'user/spot/orders_info',
|
||||
'user/request_withdrawal',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BCH/BTC': { 'id': 'bcc_btc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bcc', 'quoteId': 'btc' },
|
||||
'BCH/JPY': { 'id': 'bcc_jpy', 'symbol': 'BCH/JPY', 'base': 'BCH', 'quote': 'JPY', 'baseId': 'bcc', 'quoteId': 'jpy' },
|
||||
'MONA/BTC': { 'id': 'mona_btc', 'symbol': 'MONA/BTC', 'base': 'MONA', 'quote': 'BTC', 'baseId': 'mona', 'quoteId': 'btc' },
|
||||
'MONA/JPY': { 'id': 'mona_jpy', 'symbol': 'MONA/JPY', 'base': 'MONA', 'quote': 'JPY', 'baseId': 'mona', 'quoteId': 'jpy' },
|
||||
'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': {
|
||||
'maker': -0.02 / 100,
|
||||
'taker': 0.12 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
// 'JPY': (amount > 30000) ? 756 : 540,
|
||||
'BTC': 0.001,
|
||||
'LTC': 0.001,
|
||||
'XRP': 0.15,
|
||||
'ETH': 0.0005,
|
||||
'MONA': 0.001,
|
||||
'BCC': 0.001,
|
||||
},
|
||||
},
|
||||
},
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
},
|
||||
'exceptions': {
|
||||
'20001': AuthenticationError,
|
||||
'20002': AuthenticationError,
|
||||
'20003': AuthenticationError,
|
||||
'20005': AuthenticationError,
|
||||
'20004': InvalidNonce,
|
||||
'40020': InvalidOrder,
|
||||
'40021': InvalidOrder,
|
||||
'40025': ExchangeError,
|
||||
'40013': OrderNotFound,
|
||||
'40014': OrderNotFound,
|
||||
'50008': PermissionDenied,
|
||||
'50009': OrderNotFound,
|
||||
'50010': OrderNotFound,
|
||||
'60001': InsufficientFunds,
|
||||
'60005': InvalidOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeInteger (ticker, 'timestamp');
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'vol'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
};
|
||||
const response = await this.publicGetPairTicker (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
return this.parseTicker (data, market);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'pair': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetPairDepth (this.extend (request, params));
|
||||
const orderbook = this.safeValue (response, 'data', {});
|
||||
const timestamp = this.safeInteger (orderbook, 'timestamp');
|
||||
return this.parseOrderBook (orderbook, timestamp);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeInteger (trade, 'executed_at');
|
||||
let symbol = undefined;
|
||||
let feeCurrency = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
feeCurrency = market['quote'];
|
||||
}
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = parseFloat (this.costToPrecision (symbol, price * amount));
|
||||
}
|
||||
}
|
||||
const id = this.safeString2 (trade, 'transaction_id', 'trade_id');
|
||||
const takerOrMaker = this.safeString (trade, 'maker_taker');
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (trade, 'fee_amount_quote');
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'currency': market['quote'],
|
||||
'cost': feeCurrency,
|
||||
};
|
||||
}
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
const type = this.safeString (trade, 'type');
|
||||
const side = this.safeString (trade, 'side');
|
||||
return {
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'id': id,
|
||||
'order': orderId,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'takerOrMaker': takerOrMaker,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': fee,
|
||||
'info': trade,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
};
|
||||
const response = await this.publicGetPairTransactions (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const trades = this.safeValue (data, 'transactions', []);
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined) {
|
||||
//
|
||||
// [
|
||||
// "0.02501786",
|
||||
// "0.02501786",
|
||||
// "0.02501786",
|
||||
// "0.02501786",
|
||||
// "0.0000",
|
||||
// 1591488000000
|
||||
// ]
|
||||
//
|
||||
return [
|
||||
this.safeInteger (ohlcv, 5),
|
||||
this.safeFloat (ohlcv, 0),
|
||||
this.safeFloat (ohlcv, 1),
|
||||
this.safeFloat (ohlcv, 2),
|
||||
this.safeFloat (ohlcv, 3),
|
||||
this.safeFloat (ohlcv, 4),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
let date = this.milliseconds ();
|
||||
date = this.ymd (date);
|
||||
date = date.split ('-');
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
'candletype': this.timeframes[timeframe],
|
||||
'yyyymmdd': date.join (''),
|
||||
};
|
||||
const response = await this.publicGetPairCandlestickCandletypeYyyymmdd (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "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);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetUserAssets (params);
|
||||
const result = { 'info': response };
|
||||
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 = {
|
||||
'free': this.safeFloat (balance, 'free_amount'),
|
||||
'used': this.safeFloat (balance, 'locked_amount'),
|
||||
'total': this.safeFloat (balance, 'onhand_amount'),
|
||||
};
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'UNFILLED': 'open',
|
||||
'PARTIALLY_FILLED': 'open',
|
||||
'FULLY_FILLED': 'closed',
|
||||
'CANCELED_UNFILLED': 'canceled',
|
||||
'CANCELED_PARTIALLY_FILLED': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const id = this.safeString (order, 'order_id');
|
||||
const marketId = this.safeString (order, 'pair');
|
||||
let symbol = undefined;
|
||||
if (marketId && !market && (marketId in this.marketsById)) {
|
||||
market = this.marketsById[marketId];
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeInteger (order, 'ordered_at');
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const amount = this.safeFloat (order, 'start_amount');
|
||||
const filled = this.safeFloat (order, 'executed_amount');
|
||||
const remaining = this.safeFloat (order, 'remaining_amount');
|
||||
const average = this.safeFloat (order, 'average_price');
|
||||
let cost = undefined;
|
||||
if (filled !== undefined) {
|
||||
if (average !== undefined) {
|
||||
cost = filled * average;
|
||||
}
|
||||
}
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const type = this.safeStringLower (order, 'type');
|
||||
const side = this.safeStringLower (order, 'side');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'average': average,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': undefined,
|
||||
'fee': undefined,
|
||||
'info': order,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
if (price === undefined) {
|
||||
throw new InvalidOrder (this.id + ' createOrder requires a price argument for both market and limit orders');
|
||||
}
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
'amount': this.amountToPrecision (symbol, amount),
|
||||
'price': this.priceToPrecision (symbol, price),
|
||||
'side': side,
|
||||
'type': type,
|
||||
};
|
||||
const response = await this.privatePostUserSpotOrder (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'order_id': id,
|
||||
'pair': market['id'],
|
||||
};
|
||||
const response = await this.privatePostUserSpotCancelOrder (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
return data;
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'order_id': id,
|
||||
'pair': market['id'],
|
||||
};
|
||||
const response = await this.privateGetUserSpotOrder (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['count'] = limit;
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['since'] = parseInt (since / 1000);
|
||||
}
|
||||
const response = await this.privateGetUserSpotActiveOrders (this.extend (request, params));
|
||||
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 = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const request = {};
|
||||
if (market !== undefined) {
|
||||
request['pair'] = market['id'];
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['count'] = limit;
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['since'] = parseInt (since / 1000);
|
||||
}
|
||||
const response = await this.privateGetUserSpotTradeHistory (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const trades = this.safeValue (data, 'trades', []);
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'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 = this.safeValue (data, 'accounts', []);
|
||||
const firstAccount = this.safeValue (accounts, 0, {});
|
||||
const address = this.safeString (firstAccount, 'address');
|
||||
return {
|
||||
'currency': currency,
|
||||
'address': address,
|
||||
'tag': undefined,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
async withdraw (code, amount, address, tag = undefined, params = {}) {
|
||||
if (!('uuid' in params)) {
|
||||
throw new ExchangeError (this.id + ' uuid is required for withdrawal');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'asset': currency['id'],
|
||||
'amount': amount,
|
||||
};
|
||||
const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const txid = this.safeString (data, 'txid');
|
||||
return {
|
||||
'info': response,
|
||||
'id': txid,
|
||||
};
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let query = this.omit (params, this.extractParams (path));
|
||||
let url = this.urls['api'][api] + '/';
|
||||
if (api === 'public') {
|
||||
url += this.implodeParams (path, params);
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
let auth = nonce;
|
||||
url += this.version + '/' + this.implodeParams (path, params);
|
||||
if (method === 'POST') {
|
||||
body = this.json (query);
|
||||
auth += body;
|
||||
} else {
|
||||
auth += '/' + this.version + '/' + path;
|
||||
if (Object.keys (query).length) {
|
||||
query = this.urlencode (query);
|
||||
url += '?' + query;
|
||||
auth += '?' + query;
|
||||
}
|
||||
}
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'ACCESS-KEY': this.apiKey,
|
||||
'ACCESS-NONCE': nonce,
|
||||
'ACCESS-SIGNATURE': this.hmac (this.encode (auth), this.encode (this.secret)),
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
const success = this.safeInteger (response, 'success');
|
||||
const data = this.safeValue (response, 'data');
|
||||
if (!success || !data) {
|
||||
const errorMessages = {
|
||||
'10000': 'URL does not exist',
|
||||
'10001': 'A system error occurred. Please contact support',
|
||||
'10002': 'Invalid JSON format. Please check the contents of transmission',
|
||||
'10003': 'A system error occurred. Please contact support',
|
||||
'10005': 'A timeout error occurred. Please wait for a while and try again',
|
||||
'20001': 'API authentication failed',
|
||||
'20002': 'Illegal API key',
|
||||
'20003': 'API key does not exist',
|
||||
'20004': 'API Nonce does not exist',
|
||||
'20005': 'API signature does not exist',
|
||||
'20011': 'Two-step verification failed',
|
||||
'20014': 'SMS authentication failed',
|
||||
'30001': 'Please specify the order quantity',
|
||||
'30006': 'Please specify the order ID',
|
||||
'30007': 'Please specify the order ID array',
|
||||
'30009': 'Please specify the stock',
|
||||
'30012': 'Please specify the order price',
|
||||
'30013': 'Trade Please specify either',
|
||||
'30015': 'Please specify the order type',
|
||||
'30016': 'Please specify asset name',
|
||||
'30019': 'Please specify uuid',
|
||||
'30039': 'Please specify the amount to be withdrawn',
|
||||
'40001': 'The order quantity is invalid',
|
||||
'40006': 'Count value is invalid',
|
||||
'40007': 'End time is invalid',
|
||||
'40008': 'end_id Value is invalid',
|
||||
'40009': 'The from_id value is invalid',
|
||||
'40013': 'The order ID is invalid',
|
||||
'40014': 'The order ID array is invalid',
|
||||
'40015': 'Too many specified orders',
|
||||
'40017': 'Incorrect issue name',
|
||||
'40020': 'The order price is invalid',
|
||||
'40021': 'The trading classification is invalid',
|
||||
'40022': 'Start date is invalid',
|
||||
'40024': 'The order type is invalid',
|
||||
'40025': 'Incorrect asset name',
|
||||
'40028': 'uuid is invalid',
|
||||
'40048': 'The amount of withdrawal is illegal',
|
||||
'50003': 'Currently, this account is in a state where you can not perform the operation you specified. Please contact support',
|
||||
'50004': 'Currently, this account is temporarily registered. Please try again after registering your account',
|
||||
'50005': 'Currently, this account is locked. Please contact support',
|
||||
'50006': 'Currently, this account is locked. Please contact support',
|
||||
'50008': 'User identification has not been completed',
|
||||
'50009': 'Your order does not exist',
|
||||
'50010': 'Can not cancel specified order',
|
||||
'50011': 'API not found',
|
||||
'60001': 'The number of possessions is insufficient',
|
||||
'60002': 'It exceeds the quantity upper limit of the tender buying order',
|
||||
'60003': 'The specified quantity exceeds the limit',
|
||||
'60004': 'The specified quantity is below the threshold',
|
||||
'60005': 'The specified price is above the limit',
|
||||
'60006': 'The specified price is below the lower limit',
|
||||
'70001': 'A system error occurred. Please contact support',
|
||||
'70002': 'A system error occurred. Please contact support',
|
||||
'70003': 'A system error occurred. Please contact support',
|
||||
'70004': 'We are unable to accept orders as the transaction is currently suspended',
|
||||
'70005': 'Order can not be accepted because purchase order is currently suspended',
|
||||
'70006': 'We can not accept orders because we are currently unsubscribed ',
|
||||
'70009': 'We are currently temporarily restricting orders to be carried out. Please use the limit order.',
|
||||
'70010': 'We are temporarily raising the minimum order quantity as the system load is now rising.',
|
||||
};
|
||||
const errorClasses = this.exceptions;
|
||||
const code = this.safeString (data, 'code');
|
||||
const message = this.safeString (errorMessages, code, 'Error');
|
||||
const ErrorClass = this.safeValue (errorClasses, code);
|
||||
if (ErrorClass !== undefined) {
|
||||
throw new ErrorClass (message);
|
||||
} else {
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+1272
File diff suppressed because it is too large.
Load diff
+34
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+1263
File diff suppressed because it is too large.
Load diff
+1537
File diff suppressed because it is too large.
Load diff
+523
@@ -0,0 +1,523 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ArgumentsRequired, OrderNotFound } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitflyer extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitflyer',
|
||||
'name': 'bitFlyer',
|
||||
'countries': [ 'JP' ],
|
||||
'version': 'v1',
|
||||
'rateLimit': 1000, // their nonce-timestamp is in seconds...
|
||||
'hostname': 'bitflyer.com', // or bitflyer.com
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': false,
|
||||
'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.{hostname}',
|
||||
'www': 'https://bitflyer.com',
|
||||
'doc': 'https://lightning.bitflyer.com/docs?lang=en',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'getmarkets/usa', // new (wip)
|
||||
'getmarkets/eu', // new (wip)
|
||||
'getmarkets', // or 'markets'
|
||||
'getboard', // ...
|
||||
'getticker',
|
||||
'getexecutions',
|
||||
'gethealth',
|
||||
'getboardstate',
|
||||
'getchats',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'getpermissions',
|
||||
'getbalance',
|
||||
'getbalancehistory',
|
||||
'getcollateral',
|
||||
'getcollateralhistory',
|
||||
'getcollateralaccounts',
|
||||
'getaddresses',
|
||||
'getcoinins',
|
||||
'getcoinouts',
|
||||
'getbankaccounts',
|
||||
'getdeposits',
|
||||
'getwithdrawals',
|
||||
'getchildorders',
|
||||
'getparentorders',
|
||||
'getparentorder',
|
||||
'getexecutions',
|
||||
'getpositions',
|
||||
'gettradingcommission',
|
||||
],
|
||||
'post': [
|
||||
'sendcoin',
|
||||
'withdraw',
|
||||
'sendchildorder',
|
||||
'cancelchildorder',
|
||||
'sendparentorder',
|
||||
'cancelparentorder',
|
||||
'cancelallchildorders',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.2 / 100,
|
||||
'taker': 0.2 / 100,
|
||||
},
|
||||
'BTC/JPY': {
|
||||
'maker': 0.15 / 100,
|
||||
'taker': 0.15 / 100,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const jp_markets = await this.publicGetGetmarkets (params);
|
||||
const us_markets = await this.publicGetGetmarketsUsa (params);
|
||||
const eu_markets = await this.publicGetGetmarketsEu (params);
|
||||
let markets = this.arrayConcat (jp_markets, us_markets);
|
||||
markets = this.arrayConcat (markets, eu_markets);
|
||||
const result = [];
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const id = this.safeString (market, 'product_code');
|
||||
const currencies = id.split ('_');
|
||||
let baseId = undefined;
|
||||
let quoteId = undefined;
|
||||
let base = undefined;
|
||||
let quote = undefined;
|
||||
const numCurrencies = currencies.length;
|
||||
if (numCurrencies === 1) {
|
||||
baseId = id.slice (0, 3);
|
||||
quoteId = id.slice (3, 6);
|
||||
} else if (numCurrencies === 2) {
|
||||
baseId = currencies[0];
|
||||
quoteId = currencies[1];
|
||||
} else {
|
||||
baseId = currencies[1];
|
||||
quoteId = currencies[2];
|
||||
}
|
||||
base = this.safeCurrencyCode (baseId);
|
||||
quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = (numCurrencies === 2) ? (base + '/' + quote) : id;
|
||||
const fees = this.safeValue (this.fees, symbol, this.fees['trading']);
|
||||
let maker = this.safeValue (fees, 'maker', this.fees['trading']['maker']);
|
||||
let taker = this.safeValue (fees, 'taker', this.fees['trading']['taker']);
|
||||
let spot = true;
|
||||
let future = false;
|
||||
let type = 'spot';
|
||||
if (('alias' in market) || (currencies[0] === 'FX')) {
|
||||
type = 'future';
|
||||
future = true;
|
||||
spot = false;
|
||||
maker = 0.0;
|
||||
taker = 0.0;
|
||||
}
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'maker': maker,
|
||||
'taker': taker,
|
||||
'type': type,
|
||||
'spot': spot,
|
||||
'future': future,
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetGetbalance (params);
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "currency_code": "JPY",
|
||||
// "amount": 1024078,
|
||||
// "available": 508000
|
||||
// },
|
||||
// {
|
||||
// "currency_code": "BTC",
|
||||
// "amount": 10.24,
|
||||
// "available": 4.12
|
||||
// },
|
||||
// {
|
||||
// "currency_code": "ETH",
|
||||
// "amount": 20.48,
|
||||
// "available": 16.38
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
const result = { 'info': response };
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const balance = response[i];
|
||||
const currencyId = this.safeString (balance, 'currency_code');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['total'] = this.safeFloat (balance, 'amount');
|
||||
account['free'] = this.safeFloat (balance, 'available');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'product_code': this.marketId (symbol),
|
||||
};
|
||||
const orderbook = await this.publicGetGetboard (this.extend (request, params));
|
||||
return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'size');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'product_code': this.marketId (symbol),
|
||||
};
|
||||
const ticker = await this.publicGetGetticker (this.extend (request, params));
|
||||
const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp'));
|
||||
const last = this.safeFloat (ticker, 'ltp');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': this.safeFloat (ticker, 'best_bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'best_ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'volume_by_product'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
let side = this.safeStringLower (trade, 'side');
|
||||
if (side !== undefined) {
|
||||
if (side.length < 1) {
|
||||
side = undefined;
|
||||
}
|
||||
}
|
||||
let order = undefined;
|
||||
if (side !== undefined) {
|
||||
const id = side + '_child_order_acceptance_id';
|
||||
if (id in trade) {
|
||||
order = trade[id];
|
||||
}
|
||||
}
|
||||
if (order === undefined) {
|
||||
order = this.safeString (trade, 'child_order_acceptance_id');
|
||||
}
|
||||
const timestamp = this.parse8601 (this.safeString (trade, 'exec_date'));
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'size');
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
const id = this.safeString (trade, 'id');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': order,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'product_code': market['id'],
|
||||
};
|
||||
const response = await this.publicGetGetexecutions (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'product_code': this.marketId (symbol),
|
||||
'child_order_type': type.toUpperCase (),
|
||||
'side': side.toUpperCase (),
|
||||
'price': price,
|
||||
'size': amount,
|
||||
};
|
||||
const result = await this.privatePostSendchildorder (this.extend (request, params));
|
||||
// { "status": - 200, "error_message": "Insufficient funds", "data": null }
|
||||
const id = this.safeString (result, 'child_order_acceptance_id');
|
||||
return {
|
||||
'info': result,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' cancelOrder() requires a `symbol` argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'product_code': this.marketId (symbol),
|
||||
'child_order_acceptance_id': id,
|
||||
};
|
||||
return await this.privatePostCancelchildorder (this.extend (request, params));
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'ACTIVE': 'open',
|
||||
'COMPLETED': 'closed',
|
||||
'CANCELED': 'canceled',
|
||||
'EXPIRED': 'canceled',
|
||||
'REJECTED': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (order, 'child_order_date'));
|
||||
const amount = this.safeFloat (order, 'size');
|
||||
const remaining = this.safeFloat (order, 'outstanding_size');
|
||||
const filled = this.safeFloat (order, 'executed_size');
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const cost = price * filled;
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'child_order_state'));
|
||||
const type = this.safeStringLower (order, 'child_order_type');
|
||||
const side = this.safeStringLower (order, 'side');
|
||||
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) {
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': undefined,
|
||||
'rate': undefined,
|
||||
};
|
||||
}
|
||||
const id = this.safeString (order, 'child_order_acceptance_id');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'info': order,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'fee': fee,
|
||||
'average': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = 100, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOrders() requires a `symbol` argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'product_code': market['id'],
|
||||
'count': limit,
|
||||
};
|
||||
const response = await this.privateGetGetchildorders (this.extend (request, params));
|
||||
let orders = this.parseOrders (response, market, since, limit);
|
||||
if (symbol !== undefined) {
|
||||
orders = this.filterBy (orders, 'symbol', symbol);
|
||||
}
|
||||
return orders;
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = 100, params = {}) {
|
||||
const request = {
|
||||
'child_order_state': 'ACTIVE',
|
||||
};
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = 100, params = {}) {
|
||||
const request = {
|
||||
'child_order_state': 'COMPLETED',
|
||||
};
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOrder() requires a `symbol` argument');
|
||||
}
|
||||
const orders = await this.fetchOrders (symbol);
|
||||
const ordersById = this.indexBy (orders, 'id');
|
||||
if (id in ordersById) {
|
||||
return ordersById[id];
|
||||
}
|
||||
throw new OrderNotFound (this.id + ' No order found with id ' + id);
|
||||
}
|
||||
|
||||
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 = {
|
||||
'product_code': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['count'] = limit;
|
||||
}
|
||||
const response = await this.privateGetGetexecutions (this.extend (request, params));
|
||||
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 ();
|
||||
if (code !== 'JPY' && code !== 'USD' && code !== 'EUR') {
|
||||
throw new ExchangeError (this.id + ' allows withdrawing JPY, USD, EUR only, ' + code + ' is not supported');
|
||||
}
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'currency_code': currency['id'],
|
||||
'amount': amount,
|
||||
// 'bank_account_id': 1234,
|
||||
};
|
||||
const response = await this.privatePostWithdraw (this.extend (request, params));
|
||||
const id = this.safeString (response, 'message_id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let request = '/' + this.version + '/';
|
||||
if (api === 'private') {
|
||||
request += 'me/';
|
||||
}
|
||||
request += path;
|
||||
if (method === 'GET') {
|
||||
if (Object.keys (params).length) {
|
||||
request += '?' + this.urlencode (params);
|
||||
}
|
||||
}
|
||||
const baseUrl = this.implodeParams (this.urls['api'], { 'hostname': this.hostname });
|
||||
const url = baseUrl + request;
|
||||
if (api === 'private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
let auth = [ nonce, method, request ].join ('');
|
||||
if (Object.keys (params).length) {
|
||||
if (method !== 'GET') {
|
||||
body = this.json (params);
|
||||
auth += body;
|
||||
}
|
||||
}
|
||||
headers = {
|
||||
'ACCESS-KEY': this.apiKey,
|
||||
'ACCESS-TIMESTAMP': nonce,
|
||||
'ACCESS-SIGN': this.hmac (this.encode (auth), this.encode (this.secret)),
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+628
@@ -0,0 +1,628 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, OrderNotFound, InsufficientFunds, DDoSProtection, PermissionDenied } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitforex extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitforex',
|
||||
'name': 'Bitforex',
|
||||
'countries': [ 'CN' ],
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'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,
|
||||
'fetchTrades': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': '1min',
|
||||
'5m': '5min',
|
||||
'15m': '15min',
|
||||
'30m': '30min',
|
||||
'1h': '1hour',
|
||||
'2h': '2hour',
|
||||
'4h': '4hour',
|
||||
'12h': '12hour',
|
||||
'1d': '1day',
|
||||
'1w': '1week',
|
||||
'1M': '1month',
|
||||
},
|
||||
'urls': {
|
||||
'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',
|
||||
'fees': 'https://help.bitforex.com/en_us/?cat=13',
|
||||
'referral': 'https://www.bitforex.com/en/invitationRegister?inviterId=1867438',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'api/v1/market/symbols',
|
||||
'api/v1/market/ticker',
|
||||
'api/v1/market/depth',
|
||||
'api/v1/market/trades',
|
||||
'api/v1/market/kline',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'api/v1/fund/mainAccount',
|
||||
'api/v1/fund/allAccount',
|
||||
'api/v1/trade/placeOrder',
|
||||
'api/v1/trade/placeMultiOrder',
|
||||
'api/v1/trade/cancelOrder',
|
||||
'api/v1/trade/orderInfo',
|
||||
'api/v1/trade/orderInfos',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'maker': 0.1 / 100,
|
||||
'taker': 0.1 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'deposit': {},
|
||||
'withdraw': {
|
||||
'BTC': 0.0005,
|
||||
'ETH': 0.01,
|
||||
'BCH': 0.0001,
|
||||
'LTC': 0.001,
|
||||
'ETC': 0.005,
|
||||
'USDT': 5,
|
||||
'CMCT': 30,
|
||||
'AION': 3,
|
||||
'LVT': 0,
|
||||
'DATA': 40,
|
||||
'RHP': 50,
|
||||
'NEO': 0,
|
||||
'AIDOC': 10,
|
||||
'BQT': 2,
|
||||
'R': 2,
|
||||
'DPY': 0.8,
|
||||
'GTC': 40,
|
||||
'AGI': 30,
|
||||
'DENT': 100,
|
||||
'SAN': 1,
|
||||
'SPANK': 8,
|
||||
'AID': 5,
|
||||
'OMG': 0.1,
|
||||
'BFT': 5,
|
||||
'SHOW': 150,
|
||||
'TRX': 20,
|
||||
'ABYSS': 10,
|
||||
'THM': 25,
|
||||
'ZIL': 20,
|
||||
'PPT': 0.2,
|
||||
'WTC': 0.4,
|
||||
'LRC': 7,
|
||||
'BNT': 1,
|
||||
'CTXC': 1,
|
||||
'MITH': 20,
|
||||
'TRUE': 4,
|
||||
'LYM': 10,
|
||||
'VEE': 100,
|
||||
'AUTO': 200,
|
||||
'REN': 50,
|
||||
'TIO': 2.5,
|
||||
'NGC': 1.5,
|
||||
'PST': 10,
|
||||
'CRE': 200,
|
||||
'IPC': 5,
|
||||
'PTT': 1000,
|
||||
'XMCT': 20,
|
||||
'ATMI': 40,
|
||||
'TERN': 40,
|
||||
'XLM': 0.01,
|
||||
'ODE': 15,
|
||||
'FTM': 100,
|
||||
'RTE': 100,
|
||||
'DCC': 100,
|
||||
'IMT': 500,
|
||||
'GOT': 3,
|
||||
'EGT': 500,
|
||||
'DACC': 1000,
|
||||
'UBEX': 500,
|
||||
'ABL': 100,
|
||||
'OLT': 100,
|
||||
'DAV': 40,
|
||||
'THRT': 10,
|
||||
'RMESH': 3,
|
||||
'UPP': 20,
|
||||
'SDT': 0,
|
||||
'SHR': 10,
|
||||
'MTV': 3,
|
||||
'ESS': 100,
|
||||
'MET': 3,
|
||||
'TTC': 20,
|
||||
'LXT': 10,
|
||||
'XCLP': 100,
|
||||
'LUK': 100,
|
||||
'UBC': 100,
|
||||
'DTX': 10,
|
||||
'BEAT': 20,
|
||||
'DEED': 2,
|
||||
'BGX': 3000,
|
||||
'PRL': 20,
|
||||
'ELY': 50,
|
||||
'CARD': 300,
|
||||
'SQR': 15,
|
||||
'VRA': 400,
|
||||
'BWX': 3500,
|
||||
'MAS': 75,
|
||||
'FLP': 0.6,
|
||||
'UNC': 300,
|
||||
'CRNC': 15,
|
||||
'MFG': 70,
|
||||
'ZXC': 70,
|
||||
'TRT': 30,
|
||||
'ZIX': 35,
|
||||
'XRA': 10,
|
||||
'AMO': 1600,
|
||||
'IPG': 3,
|
||||
'uDoo': 50,
|
||||
'URB': 30,
|
||||
'ARCONA': 3,
|
||||
'CRAD': 5,
|
||||
'NOBS': 1000,
|
||||
'ADF': 2,
|
||||
'ELF': 5,
|
||||
'LX': 20,
|
||||
'PATH': 15,
|
||||
'SILK': 120,
|
||||
'SKYFT': 50,
|
||||
'EDN': 50,
|
||||
'ADE': 50,
|
||||
'EDR': 10,
|
||||
'TIME': 0.25,
|
||||
'SPRK': 20,
|
||||
'QTUM': 0.01,
|
||||
'BF': 5,
|
||||
'ZPR': 100,
|
||||
'HYB': 10,
|
||||
'CAN': 30,
|
||||
'CEL': 10,
|
||||
'ATS': 50,
|
||||
'KCASH': 1,
|
||||
'ACT': 0.01,
|
||||
'MT': 300,
|
||||
'DXT': 30,
|
||||
'WAB': 4000,
|
||||
'HYDRO': 400,
|
||||
'LQD': 5,
|
||||
'OPTC': 200,
|
||||
'EQUAD': 80,
|
||||
'LATX': 50,
|
||||
'LEDU': 100,
|
||||
'RIT': 70,
|
||||
'ACDC': 500,
|
||||
'FSN': 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
'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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetApiV1MarketSymbols (params);
|
||||
const data = response['data'];
|
||||
const result = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const market = data[i];
|
||||
const id = this.safeString (market, 'symbol');
|
||||
const symbolParts = id.split ('-');
|
||||
const baseId = symbolParts[2];
|
||||
const quoteId = symbolParts[1];
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const active = true;
|
||||
const precision = {
|
||||
'amount': this.safeInteger (market, 'amountPrecision'),
|
||||
'price': this.safeInteger (market, 'pricePrecision'),
|
||||
};
|
||||
const limits = {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'minOrderAmount'),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'precision': precision,
|
||||
'limits': limits,
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeInteger (trade, 'time');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const orderId = undefined;
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
const sideId = this.safeInteger (trade, 'direction');
|
||||
const side = this.parseSide (sideId);
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'order': orderId,
|
||||
'fee': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['size'] = limit;
|
||||
}
|
||||
const market = this.market (symbol);
|
||||
const response = await this.publicGetApiV1MarketTrades (this.extend (request, params));
|
||||
return this.parseTrades (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostApiV1FundAllAccount (params);
|
||||
const data = response['data'];
|
||||
const result = { 'info': response };
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const balance = data[i];
|
||||
const currencyId = this.safeString (balance, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['used'] = this.safeFloat (balance, 'frozen');
|
||||
account['free'] = this.safeFloat (balance, 'active');
|
||||
account['total'] = this.safeFloat (balance, 'fix');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.markets[symbol];
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const response = await this.publicGetApiV1MarketTicker (this.extend (request, params));
|
||||
const data = response['data'];
|
||||
const timestamp = this.safeInteger (data, 'date');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (data, 'high'),
|
||||
'low': this.safeFloat (data, 'low'),
|
||||
'bid': this.safeFloat (data, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (data, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': this.safeFloat (data, 'last'),
|
||||
'last': this.safeFloat (data, 'last'),
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (data, 'vol'),
|
||||
'quoteVolume': undefined,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
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'),
|
||||
this.safeFloat (ohlcv, 'high'),
|
||||
this.safeFloat (ohlcv, 'low'),
|
||||
this.safeFloat (ohlcv, 'close'),
|
||||
this.safeFloat (ohlcv, 'vol'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'ktype': this.timeframes[timeframe],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['size'] = limit; // default 1, max 600
|
||||
}
|
||||
const response = await this.publicGetApiV1MarketKline (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "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 = {}) {
|
||||
await this.loadMarkets ();
|
||||
const marketId = this.marketId (symbol);
|
||||
const request = {
|
||||
'symbol': marketId,
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['size'] = limit;
|
||||
}
|
||||
const response = await this.publicGetApiV1MarketDepth (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
const timestamp = this.safeInteger (response, 'time');
|
||||
return this.parseOrderBook (data, timestamp, 'bids', 'asks', 'price', 'amount');
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'0': 'open',
|
||||
'1': 'open',
|
||||
'2': 'closed',
|
||||
'3': 'canceled',
|
||||
'4': 'canceled',
|
||||
};
|
||||
return (status in statuses) ? statuses[status] : status;
|
||||
}
|
||||
|
||||
parseSide (sideId) {
|
||||
if (sideId === 1) {
|
||||
return 'buy';
|
||||
} else if (sideId === 2) {
|
||||
return 'sell';
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const id = this.safeString (order, 'orderId');
|
||||
const timestamp = this.safeFloat (order, 'createTime');
|
||||
const lastTradeTimestamp = this.safeFloat (order, 'lastTime');
|
||||
const symbol = market['symbol'];
|
||||
const sideId = this.safeInteger (order, 'tradeType');
|
||||
const side = this.parseSide (sideId);
|
||||
const type = undefined;
|
||||
const price = this.safeFloat (order, 'orderPrice');
|
||||
const average = this.safeFloat (order, 'avgPrice');
|
||||
const amount = this.safeFloat (order, 'orderAmount');
|
||||
const filled = this.safeFloat (order, 'dealAmount');
|
||||
const remaining = amount - filled;
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'orderState'));
|
||||
const cost = filled * price;
|
||||
const feeSide = (side === 'buy') ? 'base' : 'quote';
|
||||
const feeCurrency = market[feeSide];
|
||||
const fee = {
|
||||
'cost': this.safeFloat (order, 'tradeFee'),
|
||||
'currency': feeCurrency,
|
||||
};
|
||||
const result = {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': lastTradeTimestamp,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'average': average,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'fee': fee,
|
||||
'trades': undefined,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'orderId': id,
|
||||
};
|
||||
const response = await this.privatePostApiV1TradeOrderInfo (this.extend (request, params));
|
||||
const order = this.parseOrder (response['data'], market);
|
||||
return order;
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'state': 0,
|
||||
};
|
||||
const response = await this.privatePostApiV1TradeOrderInfos (this.extend (request, params));
|
||||
return this.parseOrders (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'state': 1,
|
||||
};
|
||||
const response = await this.privatePostApiV1TradeOrderInfos (this.extend (request, params));
|
||||
return this.parseOrders (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let sideId = undefined;
|
||||
if (side === 'buy') {
|
||||
sideId = 1;
|
||||
} else if (side === 'sell') {
|
||||
sideId = 2;
|
||||
}
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'tradeType': sideId,
|
||||
};
|
||||
const response = await this.privatePostApiV1TradePlaceOrder (this.extend (request, params));
|
||||
const data = response['data'];
|
||||
return {
|
||||
'info': response,
|
||||
'id': this.safeString (data, 'orderId'),
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'orderId': id,
|
||||
};
|
||||
if (symbol !== undefined) {
|
||||
request['symbol'] = this.marketId (symbol);
|
||||
}
|
||||
const results = await this.privatePostApiV1TradeCancelOrder (this.extend (request, params));
|
||||
const success = results['success'];
|
||||
const returnVal = { 'info': results, 'success': success };
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
let payload = this.urlencode ({ 'accessKey': this.apiKey });
|
||||
query['nonce'] = this.milliseconds ();
|
||||
if (Object.keys (query).length) {
|
||||
payload += '&' + this.urlencode (this.keysort (query));
|
||||
}
|
||||
// let message = '/' + 'api/' + this.version + '/' + path + '?' + payload;
|
||||
const message = '/' + path + '?' + payload;
|
||||
const signature = this.hmac (this.encode (message), this.encode (this.secret));
|
||||
body = payload + '&signData=' + signature;
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (typeof body !== 'string') {
|
||||
return; // fallback to default error handler
|
||||
}
|
||||
if ((body[0] === '{') || (body[0] === '[')) {
|
||||
const feedback = this.id + ' ' + body;
|
||||
const success = this.safeValue (response, 'success');
|
||||
if (success !== undefined) {
|
||||
if (!success) {
|
||||
const code = this.safeString (response, 'code');
|
||||
this.throwExactlyMatchedException (this.exceptions, code, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+2904
File diff suppressed because it is too large.
Load diff
+886
@@ -0,0 +1,886 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bithumb extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bithumb',
|
||||
'name': 'Bithumb',
|
||||
'countries': [ 'KR' ], // South Korea
|
||||
'rateLimit': 500,
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': true,
|
||||
'createMarketOrder': true,
|
||||
'createOrder': true,
|
||||
'fetchBalance': true,
|
||||
'fetchMarkets': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTickers': true,
|
||||
'fetchTrades': true,
|
||||
'withdraw': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.bithumb.com/public',
|
||||
'private': 'https://api.bithumb.com',
|
||||
},
|
||||
'www': 'https://www.bithumb.com',
|
||||
'doc': 'https://apidocs.bithumb.com',
|
||||
'fees': 'https://en.bithumb.com/customer_support/info_fee',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'ticker/{currency}',
|
||||
'ticker/all',
|
||||
'orderbook/{currency}',
|
||||
'orderbook/all',
|
||||
'transaction_history/{currency}',
|
||||
'transaction_history/all',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'info/account',
|
||||
'info/balance',
|
||||
'info/wallet_address',
|
||||
'info/ticker',
|
||||
'info/orders',
|
||||
'info/user_transactions',
|
||||
'info/order_detail',
|
||||
'trade/place',
|
||||
'trade/cancel',
|
||||
'trade/btc_withdrawal',
|
||||
'trade/krw_deposit',
|
||||
'trade/krw_withdrawal',
|
||||
'trade/market_buy',
|
||||
'trade/market_sell',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.25 / 100,
|
||||
'taker': 0.25 / 100,
|
||||
},
|
||||
},
|
||||
'precisionMode': SIGNIFICANT_DIGITS,
|
||||
'exceptions': {
|
||||
'Bad Request(SSL)': BadRequest,
|
||||
'Bad Request(Bad Method)': BadRequest,
|
||||
'Bad Request.(Auth Data)': AuthenticationError, // { "status": "5100", "message": "Bad Request.(Auth Data)" }
|
||||
'Not Member': AuthenticationError,
|
||||
'Invalid Apikey': AuthenticationError, // {"status":"5300","message":"Invalid Apikey"}
|
||||
'Method Not Allowed.(Access IP)': PermissionDenied,
|
||||
'Method Not Allowed.(BTC Adress)': InvalidAddress,
|
||||
'Method Not Allowed.(Access)': PermissionDenied,
|
||||
'Database Fail': ExchangeNotAvailable,
|
||||
'Invalid Parameter': BadRequest,
|
||||
'5600': ExchangeError,
|
||||
'Unknown Error': ExchangeError,
|
||||
'After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions': ExchangeError, // {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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 = this.safeCurrencyCode (currencyId);
|
||||
const symbol = currencyId + '/' + quote;
|
||||
let active = true;
|
||||
if (Array.isArray (market)) {
|
||||
const numElements = market.length;
|
||||
if (numElements === 0) {
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
result.push ({
|
||||
'id': currencyId,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'info': market,
|
||||
'active': active,
|
||||
'precision': {
|
||||
'amount': 4,
|
||||
'price': 4,
|
||||
},
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': 500,
|
||||
'max': 5000000000,
|
||||
},
|
||||
},
|
||||
'baseId': undefined,
|
||||
'quoteId': undefined,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'currency': 'ALL',
|
||||
};
|
||||
const response = await this.privatePostInfoBalance (this.extend (request, params));
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'data');
|
||||
const codes = Object.keys (this.currencies);
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const account = this.account ();
|
||||
const currency = this.currency (code);
|
||||
const lowerCurrencyId = this.safeStringLower (currency, 'id');
|
||||
account['total'] = this.safeFloat (balances, 'total_' + lowerCurrencyId);
|
||||
account['used'] = this.safeFloat (balances, 'in_use_' + lowerCurrencyId);
|
||||
account['free'] = this.safeFloat (balances, 'available_' + lowerCurrencyId);
|
||||
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['base'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['count'] = limit; // default 30, max 30
|
||||
}
|
||||
const response = await this.publicGetOrderbookCurrency (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "status":"0000",
|
||||
// "data":{
|
||||
// "timestamp":"1587621553942",
|
||||
// "payment_currency":"KRW",
|
||||
// "order_currency":"BTC",
|
||||
// "bids":[
|
||||
// {"price":"8652000","quantity":"0.0043"},
|
||||
// {"price":"8651000","quantity":"0.0049"},
|
||||
// {"price":"8650000","quantity":"8.4791"},
|
||||
// ],
|
||||
// "asks":[
|
||||
// {"price":"8654000","quantity":"0.119"},
|
||||
// {"price":"8655000","quantity":"0.254"},
|
||||
// {"price":"8658000","quantity":"0.119"},
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const timestamp = this.safeInteger (data, 'timestamp');
|
||||
return this.parseOrderBook (data, timestamp, 'bids', 'asks', 'price', 'quantity');
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
//
|
||||
// fetchTicker, fetchTickers
|
||||
//
|
||||
// {
|
||||
// "opening_price":"227100",
|
||||
// "closing_price":"228400",
|
||||
// "min_price":"222300",
|
||||
// "max_price":"230000",
|
||||
// "units_traded":"82618.56075337",
|
||||
// "acc_trade_value":"18767376138.6031",
|
||||
// "prev_closing_price":"227100",
|
||||
// "units_traded_24H":"151871.13484676",
|
||||
// "acc_trade_value_24H":"34247610416.8974",
|
||||
// "fluctate_24H":"8700",
|
||||
// "fluctate_rate_24H":"3.96",
|
||||
// "date":"1587710327264", // fetchTickers inject this
|
||||
// }
|
||||
//
|
||||
const timestamp = this.safeInteger (ticker, 'date');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const open = this.safeFloat (ticker, 'opening_price');
|
||||
const close = this.safeFloat (ticker, 'closing_price');
|
||||
let change = undefined;
|
||||
let percentage = undefined;
|
||||
let average = undefined;
|
||||
if ((close !== undefined) && (open !== undefined)) {
|
||||
change = close - open;
|
||||
if (open > 0) {
|
||||
percentage = change / open * 100;
|
||||
}
|
||||
average = this.sum (open, close) / 2;
|
||||
}
|
||||
const baseVolume = this.safeFloat (ticker, 'units_traded_24H');
|
||||
const quoteVolume = this.safeFloat (ticker, 'acc_trade_value_24H');
|
||||
const vwap = this.vwap (baseVolume, quoteVolume);
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'max_price'),
|
||||
'low': this.safeFloat (ticker, 'min_price'),
|
||||
'bid': this.safeFloat (ticker, 'buy_price'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell_price'),
|
||||
'askVolume': undefined,
|
||||
'vwap': vwap,
|
||||
'open': open,
|
||||
'close': close,
|
||||
'last': close,
|
||||
'previousClose': undefined,
|
||||
'change': change,
|
||||
'percentage': percentage,
|
||||
'average': average,
|
||||
'baseVolume': baseVolume,
|
||||
'quoteVolume': quoteVolume,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetTickerAll (params);
|
||||
//
|
||||
// {
|
||||
// "status":"0000",
|
||||
// "data":{
|
||||
// "BTC":{
|
||||
// "opening_price":"9045000",
|
||||
// "closing_price":"9132000",
|
||||
// "min_price":"8938000",
|
||||
// "max_price":"9168000",
|
||||
// "units_traded":"4619.79967497",
|
||||
// "acc_trade_value":"42021363832.5187",
|
||||
// "prev_closing_price":"9041000",
|
||||
// "units_traded_24H":"8793.5045804",
|
||||
// "acc_trade_value_24H":"78933458515.4962",
|
||||
// "fluctate_24H":"530000",
|
||||
// "fluctate_rate_24H":"6.16"
|
||||
// },
|
||||
// "date":"1587710878669"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
const result = {};
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const timestamp = this.safeInteger (data, 'date');
|
||||
const tickers = this.omit (data, 'date');
|
||||
const ids = Object.keys (tickers);
|
||||
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 = tickers[id];
|
||||
const isArray = Array.isArray (ticker);
|
||||
if (!isArray) {
|
||||
ticker['date'] = timestamp;
|
||||
result[symbol] = this.parseTicker (ticker, market);
|
||||
}
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currency': market['base'],
|
||||
};
|
||||
const response = await this.publicGetTickerCurrency (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "status":"0000",
|
||||
// "data":{
|
||||
// "opening_price":"227100",
|
||||
// "closing_price":"228400",
|
||||
// "min_price":"222300",
|
||||
// "max_price":"230000",
|
||||
// "units_traded":"82618.56075337",
|
||||
// "acc_trade_value":"18767376138.6031",
|
||||
// "prev_closing_price":"227100",
|
||||
// "units_traded_24H":"151871.13484676",
|
||||
// "acc_trade_value_24H":"34247610416.8974",
|
||||
// "fluctate_24H":"8700",
|
||||
// "fluctate_rate_24H":"3.96",
|
||||
// "date":"1587710327264"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
return this.parseTicker (data, market);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
//
|
||||
// fetchTrades (public)
|
||||
//
|
||||
// {
|
||||
// "transaction_date":"2020-04-23 22:21:46",
|
||||
// "type":"ask",
|
||||
// "units_traded":"0.0125",
|
||||
// "price":"8667000",
|
||||
// "total":"108337"
|
||||
// }
|
||||
//
|
||||
// fetchOrder (private)
|
||||
//
|
||||
// {
|
||||
// "transaction_date": "1572497603902030",
|
||||
// "price": "8601000",
|
||||
// "units": "0.005",
|
||||
// "fee_currency": "KRW",
|
||||
// "fee": "107.51",
|
||||
// "total": "43005"
|
||||
// }
|
||||
//
|
||||
// a workaround for their bug in date format, hours are not 0-padded
|
||||
let timestamp = undefined;
|
||||
const transactionDatetime = this.safeString (trade, 'transaction_date');
|
||||
if (transactionDatetime !== undefined) {
|
||||
const parts = transactionDatetime.split (' ');
|
||||
const numParts = parts.length;
|
||||
if (numParts > 1) {
|
||||
const transactionDate = parts[0];
|
||||
let transactionTime = parts[1];
|
||||
if (transactionTime.length < 8) {
|
||||
transactionTime = '0' + transactionTime;
|
||||
}
|
||||
timestamp = this.parse8601 (transactionDate + ' ' + transactionTime);
|
||||
} else {
|
||||
timestamp = this.safeIntegerProduct (trade, 'transaction_date', 0.001);
|
||||
}
|
||||
}
|
||||
if (timestamp !== undefined) {
|
||||
timestamp -= 9 * 3600000; // they report UTC + 9 hours, server in Korean timezone
|
||||
}
|
||||
const type = undefined;
|
||||
let side = this.safeString (trade, 'type');
|
||||
side = (side === 'ask') ? 'sell' : 'buy';
|
||||
const id = this.safeString (trade, 'cont_no');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat2 (trade, 'units_traded', 'units');
|
||||
let cost = this.safeFloat (trade, 'total');
|
||||
if (cost === undefined) {
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (trade, 'fee');
|
||||
if (feeCost !== undefined) {
|
||||
const feeCurrencyId = this.safeString (trade, 'fee_currency');
|
||||
const feeCurrencyCode = this.commonCurrencyCode (feeCurrencyId);
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrencyCode,
|
||||
};
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': undefined,
|
||||
'type': type,
|
||||
'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['base'],
|
||||
};
|
||||
if (limit === undefined) {
|
||||
request['count'] = limit; // default 20, max 100
|
||||
}
|
||||
const response = await this.publicGetTransactionHistoryCurrency (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "status":"0000",
|
||||
// "data":[
|
||||
// {
|
||||
// "transaction_date":"2020-04-23 22:21:46",
|
||||
// "type":"ask",
|
||||
// "units_traded":"0.0125",
|
||||
// "price":"8667000",
|
||||
// "total":"108337"
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseTrades (data, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'order_currency': market['id'],
|
||||
'Payment_currency': market['quote'],
|
||||
'units': amount,
|
||||
};
|
||||
let method = 'privatePostTradePlace';
|
||||
if (type === 'limit') {
|
||||
request['price'] = price;
|
||||
request['type'] = (side === 'buy') ? 'bid' : 'ask';
|
||||
} else {
|
||||
method = 'privatePostTradeMarket' + this.capitalize (side);
|
||||
}
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
const id = this.safeString (response, 'order_id');
|
||||
if (id === undefined) {
|
||||
throw new InvalidOrder (this.id + ' createOrder did not return an order id');
|
||||
}
|
||||
return {
|
||||
'info': response,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
'count': 1,
|
||||
'order_currency': market['base'],
|
||||
'payment_currency': market['quote'],
|
||||
};
|
||||
const response = await this.privatePostInfoOrderDetail (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "status": "0000",
|
||||
// "data": {
|
||||
// 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'
|
||||
// }
|
||||
// ],
|
||||
// }
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (this.extend (data, { 'order_id': id }), market);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'Pending': 'open',
|
||||
'Completed': 'closed',
|
||||
'Cancel': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
//
|
||||
// fetchOrder
|
||||
//
|
||||
// {
|
||||
// "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": [
|
||||
// {
|
||||
// "transaction_date": "1572497603902030",
|
||||
// "price": "8601000",
|
||||
// "units": "0.005",
|
||||
// "fee_currency": "KRW",
|
||||
// "fee": "107.51",
|
||||
// "total": "43005"
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// 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
|
||||
//
|
||||
// {
|
||||
// "order_currency": "BTC",
|
||||
// "payment_currency": "KRW",
|
||||
// "order_id": "C0101000007408440032",
|
||||
// "order_date": "1571728739360570",
|
||||
// "type": "bid",
|
||||
// "units": "5.0",
|
||||
// "units_remaining": "5.0",
|
||||
// "price": "501000",
|
||||
// }
|
||||
//
|
||||
const timestamp = this.safeIntegerProduct (order, 'order_date', 0.001);
|
||||
const sideProperty = this.safeValue2 (order, 'type', 'side');
|
||||
const side = (sideProperty === 'bid') ? 'buy' : 'sell';
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'order_status'));
|
||||
let price = this.safeFloat2 (order, 'order_price', 'price');
|
||||
let type = 'limit';
|
||||
if (price === 0) {
|
||||
price = undefined;
|
||||
type = 'market';
|
||||
}
|
||||
const amount = this.safeFloat2 (order, 'order_qty', 'units');
|
||||
let remaining = this.safeFloat (order, 'units_remaining');
|
||||
if (remaining === undefined) {
|
||||
if (status === 'closed') {
|
||||
remaining = 0;
|
||||
} else if (status !== 'canceled') {
|
||||
remaining = amount;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
const baseId = this.safeString (order, 'order_currency');
|
||||
const quoteId = this.safeString (order, 'payment_currency');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
if ((base !== undefined) && (quote !== undefined)) {
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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': 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 = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
if (limit === undefined) {
|
||||
limit = 100;
|
||||
}
|
||||
const request = {
|
||||
'count': limit,
|
||||
'order_currency': market['base'],
|
||||
'payment_currency': market['quote'],
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['after'] = since;
|
||||
}
|
||||
const response = await this.privatePostInfoOrders (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "status": "0000",
|
||||
// "data": [
|
||||
// {
|
||||
// "order_currency": "BTC",
|
||||
// "payment_currency": "KRW",
|
||||
// "order_id": "C0101000007408440032",
|
||||
// "order_date": "1571728739360570",
|
||||
// "type": "bid",
|
||||
// "units": "5.0",
|
||||
// "units_remaining": "5.0",
|
||||
// "price": "501000",
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseOrders (data, market, since, limit);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const side_in_params = ('side' in params);
|
||||
if (!side_in_params) {
|
||||
throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument and a `side` parameter (sell or buy)');
|
||||
}
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument and a `side` parameter (sell or buy)');
|
||||
}
|
||||
const market = this.market (symbol);
|
||||
const side = (params['side'] === 'buy') ? 'bid' : 'ask';
|
||||
params = this.omit (params, [ 'side', 'currency' ]);
|
||||
// https://github.com/ccxt/ccxt/issues/6771
|
||||
const request = {
|
||||
'order_id': id,
|
||||
'type': side,
|
||||
'order_currency': market['base'],
|
||||
'payment_currency': market['quote'],
|
||||
};
|
||||
return await this.privatePostTradeCancel (this.extend (request, params));
|
||||
}
|
||||
|
||||
cancelUnifiedOrder (order, params = {}) {
|
||||
const request = {
|
||||
'side': order['side'],
|
||||
};
|
||||
return this.cancelOrder (order['id'], order['symbol'], this.extend (request, params));
|
||||
}
|
||||
|
||||
async withdraw (code, amount, address, tag = undefined, params = {}) {
|
||||
this.checkAddress (address);
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'units': amount,
|
||||
'address': address,
|
||||
'currency': currency['id'],
|
||||
};
|
||||
if (currency === 'XRP' || currency === 'XMR') {
|
||||
const destination = this.safeString (params, 'destination');
|
||||
if ((tag === undefined) && (destination === undefined)) {
|
||||
throw new ArgumentsRequired (this.id + ' ' + code + ' withdraw() requires a tag argument or an extra destination param');
|
||||
} else if (tag !== undefined) {
|
||||
request['destination'] = tag;
|
||||
}
|
||||
}
|
||||
const response = await this.privatePostTradeBtcWithdrawal (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const endpoint = '/' + this.implodeParams (path, params);
|
||||
let url = this.urls['api'][api] + endpoint;
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
body = this.urlencode (this.extend ({
|
||||
'endpoint': endpoint,
|
||||
}, query));
|
||||
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 (signature));
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Api-Key': this.apiKey,
|
||||
'Api-Sign': signature64,
|
||||
'Api-Nonce': nonce,
|
||||
};
|
||||
}
|
||||
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
|
||||
}
|
||||
if ('status' in response) {
|
||||
//
|
||||
// {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"}
|
||||
//
|
||||
const status = this.safeString (response, 'status');
|
||||
const message = this.safeString (response, 'message');
|
||||
if (status !== undefined) {
|
||||
if (status === '0000') {
|
||||
return; // no error
|
||||
}
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwExactlyMatchedException (this.exceptions, status, feedback);
|
||||
this.throwExactlyMatchedException (this.exceptions, message, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if ('status' in response) {
|
||||
if (response['status'] === '0000') {
|
||||
return response;
|
||||
}
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const zb = require ('./zb.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitkk extends zb {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitkk',
|
||||
'name': 'bitkk',
|
||||
'comment': 'a Chinese ZB clone',
|
||||
'urls': {
|
||||
'api': {
|
||||
'public': 'http://api.bitkk.com/data', // no https for public API
|
||||
'private': 'https://trade.bitkk.com/api',
|
||||
},
|
||||
'www': 'https://www.bitkk.com',
|
||||
'doc': 'https://www.bitkk.com/i/developer',
|
||||
'fees': 'https://www.bitkk.com/i/rate',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+2348
File diff suppressed because it is too large.
Load diff
+1725
File diff suppressed because it is too large.
Load diff
+1640
File diff suppressed because it is too large.
Load diff
+1811
File diff suppressed because it is too large.
Load diff
+622
@@ -0,0 +1,622 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, InvalidNonce, AuthenticationError, OrderNotFound } = require ('./base/errors');
|
||||
const { TICK_SIZE } = require ('./base/functions/number');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitso extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitso',
|
||||
'name': 'Bitso',
|
||||
'countries': [ 'MX' ], // Mexico
|
||||
'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/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',
|
||||
'referral': 'https://bitso.com/?ref=itej',
|
||||
},
|
||||
'precisionMode': TICK_SIZE,
|
||||
'options': {
|
||||
'precision': {
|
||||
'XRP': 0.000001,
|
||||
'MXN': 0.01,
|
||||
'TUSD': 0.01,
|
||||
},
|
||||
'defaultPrecision': 0.00000001,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'available_books',
|
||||
'ticker',
|
||||
'order_book',
|
||||
'trades',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'account_status',
|
||||
'balance',
|
||||
'fees',
|
||||
'fundings',
|
||||
'fundings/{fid}',
|
||||
'funding_destination',
|
||||
'kyc_documents',
|
||||
'ledger',
|
||||
'ledger/trades',
|
||||
'ledger/fees',
|
||||
'ledger/fundings',
|
||||
'ledger/withdrawals',
|
||||
'mx_bank_codes',
|
||||
'open_orders',
|
||||
'order_trades/{oid}',
|
||||
'orders/{oid}',
|
||||
'user_trades',
|
||||
'user_trades/{tid}',
|
||||
'withdrawals/',
|
||||
'withdrawals/{wid}',
|
||||
],
|
||||
'post': [
|
||||
'bitcoin_withdrawal',
|
||||
'debit_card_withdrawal',
|
||||
'ether_withdrawal',
|
||||
'ripple_withdrawal',
|
||||
'bcash_withdrawal',
|
||||
'litecoin_withdrawal',
|
||||
'orders',
|
||||
'phone_number',
|
||||
'phone_verification',
|
||||
'phone_withdrawal',
|
||||
'spei_withdrawal',
|
||||
'ripple_withdrawal',
|
||||
'bcash_withdrawal',
|
||||
'litecoin_withdrawal',
|
||||
],
|
||||
'delete': [
|
||||
'orders/{oid}',
|
||||
'orders/all',
|
||||
],
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'0201': AuthenticationError, // Invalid Nonce or Invalid Credentials
|
||||
'104': InvalidNonce, // Cannot perform request - nonce must be higher than 1520307203724237
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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++) {
|
||||
const market = markets[i];
|
||||
const id = this.safeString (market, 'book');
|
||||
const [ baseId, quoteId ] = id.split ('_');
|
||||
let base = baseId.toUpperCase ();
|
||||
let quote = quoteId.toUpperCase ();
|
||||
base = this.safeCurrencyCode (base);
|
||||
quote = this.safeCurrencyCode (quote);
|
||||
const symbol = base + '/' + quote;
|
||||
const limits = {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'minimum_amount'),
|
||||
'max': this.safeFloat (market, 'maximum_amount'),
|
||||
},
|
||||
'price': {
|
||||
'min': this.safeFloat (market, 'minimum_price'),
|
||||
'max': this.safeFloat (market, 'maximum_price'),
|
||||
},
|
||||
'cost': {
|
||||
'min': this.safeFloat (market, 'minimum_value'),
|
||||
'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.safeFloat (this.options['precision'], base, this.options['defaultPrecision']),
|
||||
'price': pricePrecision,
|
||||
};
|
||||
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,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'info': market,
|
||||
'limits': limits,
|
||||
'precision': precision,
|
||||
'active': undefined,
|
||||
}, fee));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetBalance (params);
|
||||
const balances = this.safeValue (response['payload'], 'balances');
|
||||
const result = { 'info': response };
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[i];
|
||||
const currencyId = this.safeString (balance, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = {
|
||||
'free': this.safeFloat (balance, 'available'),
|
||||
'used': this.safeFloat (balance, 'locked'),
|
||||
'total': this.safeFloat (balance, 'total'),
|
||||
};
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'book': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetOrderBook (this.extend (request, params));
|
||||
const orderbook = this.safeValue (response, 'payload');
|
||||
const timestamp = this.parse8601 (this.safeString (orderbook, 'updated_at'));
|
||||
return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'book': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
const ticker = this.safeValue (response, 'payload');
|
||||
const timestamp = this.parse8601 (this.safeString (ticker, 'created_at'));
|
||||
const vwap = this.safeFloat (ticker, 'vwap');
|
||||
const baseVolume = this.safeFloat (ticker, 'volume');
|
||||
let quoteVolume = undefined;
|
||||
if (baseVolume !== undefined && vwap !== undefined) {
|
||||
quoteVolume = baseVolume * vwap;
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': vwap,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': baseVolume,
|
||||
'quoteVolume': quoteVolume,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (trade, 'created_at'));
|
||||
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) {
|
||||
amount = Math.abs (amount);
|
||||
}
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (trade, 'fees_amount');
|
||||
if (feeCost !== undefined) {
|
||||
const feeCurrencyId = this.safeString (trade, 'fees_currency');
|
||||
const feeCurrency = this.safeCurrencyCode (feeCurrencyId);
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrency,
|
||||
};
|
||||
}
|
||||
let cost = this.safeFloat (trade, 'minor');
|
||||
if (cost !== undefined) {
|
||||
cost = Math.abs (cost);
|
||||
}
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const orderId = this.safeString (trade, 'oid');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': orderId,
|
||||
'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 = {
|
||||
'book': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTrades (this.extend (request, params));
|
||||
return this.parseTrades (response['payload'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = 25, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
// the don't support fetching trades starting from a date yet
|
||||
// use the `marker` extra param for that
|
||||
// this is not a typo, the variable name is 'marker' (don't confuse with 'market')
|
||||
const markerInParams = ('marker' in params);
|
||||
// warn the user with an exception if the user wants to filter
|
||||
// starting from since timestamp, but does not set the trade id with an extra 'marker' param
|
||||
if ((since !== undefined) && !markerInParams) {
|
||||
throw new ExchangeError (this.id + ' fetchMyTrades does not support fetching trades starting from a timestamp with the `since` argument, use the `marker` extra param to filter starting from an integer trade id');
|
||||
}
|
||||
// convert it to an integer unconditionally
|
||||
if (markerInParams) {
|
||||
params = this.extend (params, {
|
||||
'marker': parseInt (params['marker']),
|
||||
});
|
||||
}
|
||||
const request = {
|
||||
'book': market['id'],
|
||||
'limit': limit, // default = 25, max = 100
|
||||
// 'sort': 'desc', // default = desc
|
||||
// 'marker': id, // integer id to start from
|
||||
};
|
||||
const response = await this.privateGetUserTrades (this.extend (request, params));
|
||||
return this.parseTrades (response['payload'], market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'book': this.marketId (symbol),
|
||||
'side': side,
|
||||
'type': type,
|
||||
'major': this.amountToPrecision (symbol, amount),
|
||||
};
|
||||
if (type === 'limit') {
|
||||
request['price'] = this.priceToPrecision (symbol, price);
|
||||
}
|
||||
const response = await this.privatePostOrders (this.extend (request, params));
|
||||
const id = this.safeString (response['payload'], 'oid');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'oid': id,
|
||||
};
|
||||
return await this.privateDeleteOrdersOid (this.extend (request, params));
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'partial-fill': 'open', // this is a common substitution in ccxt
|
||||
'completed': 'closed',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const id = this.safeString (order, 'oid');
|
||||
const side = this.safeString (order, 'side');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const marketId = this.safeString (order, 'book');
|
||||
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');
|
||||
const amount = this.safeFloat (order, 'original_amount');
|
||||
const remaining = this.safeFloat (order, 'unfilled_amount');
|
||||
let filled = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (remaining !== undefined) {
|
||||
filled = amount - remaining;
|
||||
}
|
||||
}
|
||||
const clientOrderId = this.safeString (order, 'client_id');
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': clientOrderId,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': orderType,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'amount': amount,
|
||||
'cost': undefined,
|
||||
'remaining': remaining,
|
||||
'filled': filled,
|
||||
'status': status,
|
||||
'fee': undefined,
|
||||
'average': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = 25, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
// the don't support fetching trades starting from a date yet
|
||||
// use the `marker` extra param for that
|
||||
// this is not a typo, the variable name is 'marker' (don't confuse with 'market')
|
||||
const markerInParams = ('marker' in params);
|
||||
// warn the user with an exception if the user wants to filter
|
||||
// starting from since timestamp, but does not set the trade id with an extra 'marker' param
|
||||
if ((since !== undefined) && !markerInParams) {
|
||||
throw new ExchangeError (this.id + ' fetchOpenOrders does not support fetching orders starting from a timestamp with the `since` argument, use the `marker` extra param to filter starting from an integer trade id');
|
||||
}
|
||||
// convert it to an integer unconditionally
|
||||
if (markerInParams) {
|
||||
params = this.extend (params, {
|
||||
'marker': parseInt (params['marker']),
|
||||
});
|
||||
}
|
||||
const request = {
|
||||
'book': market['id'],
|
||||
'limit': limit, // default = 25, max = 100
|
||||
// 'sort': 'desc', // default = desc
|
||||
// 'marker': id, // integer id to start from
|
||||
};
|
||||
const response = await this.privateGetOpenOrders (this.extend (request, params));
|
||||
const orders = this.parseOrders (response['payload'], market, since, limit);
|
||||
return orders;
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetOrdersOid ({
|
||||
'oid': id,
|
||||
});
|
||||
const payload = this.safeValue (response, 'payload');
|
||||
if (Array.isArray (payload)) {
|
||||
const numOrders = response['payload'].length;
|
||||
if (numOrders === 1) {
|
||||
return this.parseOrder (payload[0]);
|
||||
}
|
||||
}
|
||||
throw new OrderNotFound (this.id + ': The order ' + id + ' not found.');
|
||||
}
|
||||
|
||||
async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'oid': id,
|
||||
};
|
||||
const response = await this.privateGetOrderTradesOid (this.extend (request, params));
|
||||
return this.parseTrades (response['payload'], market);
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'fund_currency': currency['id'],
|
||||
};
|
||||
const response = await this.privateGetFundingDestination (this.extend (request, params));
|
||||
let address = this.safeString (response['payload'], 'account_identifier');
|
||||
let tag = undefined;
|
||||
if (address.indexOf ('?dt=') >= 0) {
|
||||
const parts = address.split ('?dt=');
|
||||
address = this.safeString (parts, 0);
|
||||
tag = this.safeString (parts, 1);
|
||||
}
|
||||
this.checkAddress (address);
|
||||
return {
|
||||
'currency': code,
|
||||
'address': address,
|
||||
'tag': tag,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
async withdraw (code, amount, address, tag = undefined, params = {}) {
|
||||
this.checkAddress (address);
|
||||
await this.loadMarkets ();
|
||||
const methods = {
|
||||
'BTC': 'Bitcoin',
|
||||
'ETH': 'Ether',
|
||||
'XRP': 'Ripple',
|
||||
'BCH': 'Bcash',
|
||||
'LTC': 'Litecoin',
|
||||
};
|
||||
const method = (code in methods) ? methods[code] : undefined;
|
||||
if (method === undefined) {
|
||||
throw new ExchangeError (this.id + ' not valid withdraw coin: ' + code);
|
||||
}
|
||||
const request = {
|
||||
'amount': amount,
|
||||
'address': address,
|
||||
'destination_tag': tag,
|
||||
};
|
||||
const classMethod = 'privatePost' + method + 'Withdrawal';
|
||||
const response = await this[classMethod] (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': this.safeString (response['payload'], 'wid'),
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let endpoint = '/' + this.version + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (method === 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
endpoint += '?' + this.urlencode (query);
|
||||
}
|
||||
}
|
||||
const url = this.urls['api'] + endpoint;
|
||||
if (api === 'private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
let request = [ nonce, method, endpoint ].join ('');
|
||||
if (method !== 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
body = this.json (query);
|
||||
request += body;
|
||||
}
|
||||
}
|
||||
const signature = this.hmac (this.encode (request), this.encode (this.secret));
|
||||
const auth = this.apiKey + ':' + nonce + ':' + signature;
|
||||
headers = {
|
||||
'Authorization': 'Bitso ' + auth,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
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
|
||||
}
|
||||
if ('success' in response) {
|
||||
//
|
||||
// {"success":false,"error":{"code":104,"message":"Cannot perform request - nonce must be higher than 1520307203724237"}}
|
||||
//
|
||||
let success = this.safeValue (response, 'success', false);
|
||||
if (typeof success === 'string') {
|
||||
if ((success === 'true') || (success === '1')) {
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
const feedback = this.id + ' ' + this.json (response);
|
||||
const error = this.safeValue (response, 'error');
|
||||
if (error === undefined) {
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
const code = this.safeString (error, 'code');
|
||||
this.throwExactlyMatchedException (this.exceptions, code, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if ('success' in response) {
|
||||
if (response['success']) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
};
|
||||
+1605
File diff suppressed because it is too large.
Load diff
+299
@@ -0,0 +1,299 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { BadSymbol, ExchangeError, NotSupported } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitstamp1 extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitstamp1',
|
||||
'name': 'Bitstamp',
|
||||
'countries': [ 'GB' ],
|
||||
'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',
|
||||
'api': 'https://www.bitstamp.net/api',
|
||||
'www': 'https://www.bitstamp.net',
|
||||
'doc': 'https://www.bitstamp.net/api',
|
||||
},
|
||||
'requiredCredentials': {
|
||||
'apiKey': true,
|
||||
'secret': true,
|
||||
'uid': true,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'ticker',
|
||||
'ticker_hour',
|
||||
'order_book',
|
||||
'transactions',
|
||||
'eur_usd',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'balance',
|
||||
'user_transactions',
|
||||
'open_orders',
|
||||
'order_status',
|
||||
'cancel_order',
|
||||
'cancel_all_orders',
|
||||
'buy',
|
||||
'sell',
|
||||
'bitcoin_deposit_address',
|
||||
'unconfirmed_btc',
|
||||
'ripple_withdrawal',
|
||||
'ripple_address',
|
||||
'withdrawal_requests',
|
||||
'bitcoin_withdrawal',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'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 },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
if (symbol !== 'BTC/USD') {
|
||||
throw new ExchangeError (this.id + ' ' + this.version + " fetchOrderBook doesn't support " + symbol + ', use it for BTC/USD only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const orderbook = await this.publicGetOrderBook (params);
|
||||
const timestamp = this.safeTimestamp (orderbook, 'timestamp');
|
||||
return this.parseOrderBook (orderbook, timestamp);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
if (symbol !== 'BTC/USD') {
|
||||
throw new ExchangeError (this.id + ' ' + this.version + " fetchTicker doesn't support " + symbol + ', use it for BTC/USD only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const ticker = await this.publicGetTicker (params);
|
||||
const timestamp = this.safeTimestamp (ticker, 'timestamp');
|
||||
const vwap = this.safeFloat (ticker, 'vwap');
|
||||
const baseVolume = this.safeFloat (ticker, 'volume');
|
||||
let quoteVolume = undefined;
|
||||
if (baseVolume !== undefined && vwap !== undefined) {
|
||||
quoteVolume = baseVolume * vwap;
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': vwap,
|
||||
'open': this.safeFloat (ticker, 'open'),
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': baseVolume,
|
||||
'quoteVolume': quoteVolume,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp2 (trade, 'date', 'datetime');
|
||||
const side = (trade['type'] === 0) ? 'buy' : 'sell';
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
if ('currency_pair' in trade) {
|
||||
if (trade['currency_pair'] in this.markets_by_id) {
|
||||
market = this.markets_by_id[trade['currency_pair']];
|
||||
}
|
||||
}
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': orderId,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol !== 'BTC/USD') {
|
||||
throw new BadSymbol (this.id + ' ' + this.version + " fetchTrades doesn't support " + symbol + ', use it for BTC/USD only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'time': 'minute',
|
||||
};
|
||||
const response = await this.publicGetTransactions (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
const balance = await this.privatePostBalance (params);
|
||||
const result = { 'info': balance };
|
||||
const codes = Object.keys (this.currencies);
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const currency = this.currency (code);
|
||||
const currencyId = currency['id'];
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (balance, currencyId + '_available');
|
||||
account['used'] = this.safeFloat (balance, currencyId + '_reserved');
|
||||
account['total'] = this.safeFloat (balance, currencyId + '_balance');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
if (type !== 'limit') {
|
||||
throw new ExchangeError (this.id + ' ' + this.version + ' accepts limit orders only');
|
||||
}
|
||||
if (symbol !== 'BTC/USD') {
|
||||
throw new ExchangeError (this.id + ' v1 supports BTC/USD orders only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const method = 'privatePost' + this.capitalize (side);
|
||||
const request = {
|
||||
'amount': amount,
|
||||
'price': price,
|
||||
};
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
const id = this.safeString (response, 'id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
return await this.privatePostCancelOrder ({ 'id': id });
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'In Queue': 'open',
|
||||
'Open': 'open',
|
||||
'Finished': 'closed',
|
||||
'Canceled': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
async fetchOrderStatus (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privatePostOrderStatus (this.extend (request, params));
|
||||
return this.parseOrderStatus (response);
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const pair = market ? market['id'] : 'all';
|
||||
const request = {
|
||||
'id': pair,
|
||||
};
|
||||
const response = await this.privatePostOpenOrdersId (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
throw new NotSupported (this.id + ' fetchOrder is not implemented yet');
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.implodeParams (path, params);
|
||||
let query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
const auth = nonce + this.uid + this.apiKey;
|
||||
const signature = this.encode (this.hmac (this.encode (auth), this.encode (this.secret)));
|
||||
query = this.extend ({
|
||||
'key': this.apiKey,
|
||||
'signature': signature.toUpperCase (),
|
||||
'nonce': nonce,
|
||||
}, query);
|
||||
body = this.urlencode (query);
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if ('status' in response) {
|
||||
if (response['status'] === 'error') {
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+1373
File diff suppressed because it is too large.
Load diff
+1495
File diff suppressed because it is too large.
Load diff
+1285
File diff suppressed because it is too large.
Load diff
+236
@@ -0,0 +1,236 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bl3p extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bl3p',
|
||||
'name': 'BL3P',
|
||||
'countries': [ 'NL', 'EU' ], // Netherlands, EU
|
||||
'rateLimit': 1000,
|
||||
'version': '1',
|
||||
'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'
|
||||
'doc': [
|
||||
'https://github.com/BitonicNL/bl3p-api/tree/master/docs',
|
||||
'https://bl3p.eu/api',
|
||||
'https://bitonic.nl/en/api',
|
||||
],
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'{market}/ticker',
|
||||
'{market}/orderbook',
|
||||
'{market}/trades',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'{market}/money/depth/full',
|
||||
'{market}/money/order/add',
|
||||
'{market}/money/order/cancel',
|
||||
'{market}/money/order/result',
|
||||
'{market}/money/orders',
|
||||
'{market}/money/orders/history',
|
||||
'{market}/money/trades/fetch',
|
||||
'GENMKT/money/info',
|
||||
'GENMKT/money/deposit_address',
|
||||
'GENMKT/money/new_deposit_address',
|
||||
'GENMKT/money/wallet/history',
|
||||
'GENMKT/money/withdraw',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/EUR': { 'id': 'BTCEUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'BTC', 'quoteId': 'EUR', '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 },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostGENMKTMoneyInfo (params);
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const wallets = this.safeValue (data, 'wallets');
|
||||
const result = { 'info': data };
|
||||
const codes = Object.keys (this.currencies);
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const currency = this.currency (code);
|
||||
const currencyId = currency['id'];
|
||||
const wallet = this.safeValue (wallets, currencyId, {});
|
||||
const available = this.safeValue (wallet, 'available', {});
|
||||
const balance = this.safeValue (wallet, 'balance', {});
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (available, 'value');
|
||||
account['total'] = this.safeFloat (balance, 'value');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseBidAsk (bidask, priceKey = 0, amountKey = 1) {
|
||||
return [
|
||||
bidask[priceKey] / 100000.0,
|
||||
bidask[amountKey] / 100000000.0,
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketOrderbook (this.extend (request, params));
|
||||
const orderbook = this.safeValue (response, 'data');
|
||||
return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price_int', 'amount_int');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
const request = {
|
||||
'market': this.marketId (symbol),
|
||||
};
|
||||
const ticker = await this.publicGetMarketTicker (this.extend (request, params));
|
||||
const timestamp = this.safeTimestamp (ticker, 'timestamp');
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker['volume'], '24h'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const id = this.safeString (trade, 'trade_id');
|
||||
const timestamp = this.safeInteger (trade, 'date');
|
||||
let price = this.safeFloat (trade, 'price_int');
|
||||
if (price !== undefined) {
|
||||
price /= 100000.0;
|
||||
}
|
||||
let amount = this.safeFloat (trade, 'amount_int');
|
||||
if (amount !== undefined) {
|
||||
amount /= 100000000.0;
|
||||
}
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': undefined,
|
||||
'order': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
const market = this.market (symbol);
|
||||
const response = await this.publicGetMarketTrades (this.extend ({
|
||||
'market': market['id'],
|
||||
}, params));
|
||||
const result = this.parseTrades (response['data']['trades'], market, since, limit);
|
||||
return result;
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
const market = this.market (symbol);
|
||||
const order = {
|
||||
'market': market['id'],
|
||||
'amount_int': parseInt (amount * 100000000),
|
||||
'fee_currency': market['quote'],
|
||||
'type': (side === 'buy') ? 'bid' : 'ask',
|
||||
};
|
||||
if (type === 'limit') {
|
||||
order['price_int'] = parseInt (price * 100000.0);
|
||||
}
|
||||
const response = await this.privatePostMarketMoneyOrderAdd (this.extend (order, params));
|
||||
const orderId = this.safeString (response['data'], 'order_id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': orderId,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
return await this.privatePostMarketMoneyOrderCancel (this.extend (request, params));
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const request = this.implodeParams (path, params);
|
||||
let url = this.urls['api'] + '/' + this.version + '/' + request;
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
body = this.urlencode (this.extend ({ 'nonce': nonce }, query));
|
||||
const secret = this.base64ToBinary (this.secret);
|
||||
// eslint-disable-next-line quotes
|
||||
const auth = request + "\0" + body;
|
||||
const signature = this.hmac (this.encode (auth), secret, 'sha512', 'base64');
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Rest-Key': this.apiKey,
|
||||
'Rest-Sign': signature,
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+813
@@ -0,0 +1,813 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, InvalidOrder, AuthenticationError, InsufficientFunds, BadRequest } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bleutrade extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bleutrade',
|
||||
'name': 'Bleutrade',
|
||||
'countries': ['BR'], // Brazil
|
||||
'rateLimit': 1000,
|
||||
'certified': false,
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': true,
|
||||
'createLimitOrder': false,
|
||||
'createMarketOrder': false,
|
||||
'createOrder': true,
|
||||
'editOrder': false,
|
||||
'fetchBalance': true,
|
||||
'fetchClosedOrders': 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': {
|
||||
'1h': '1h',
|
||||
'4h': '4h',
|
||||
'8h': '8h',
|
||||
'1d': '1d',
|
||||
'1w': '1w',
|
||||
},
|
||||
'hostname': 'bleutrade.com',
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/30303000-b602dbe6-976d-11e7-956d-36c5049c01e7.jpg',
|
||||
'api': {
|
||||
'v3Private': 'https://{hostname}/api/v3/private',
|
||||
'v3Public': 'https://{hostname}/api/v3/public',
|
||||
},
|
||||
'www': 'https://bleutrade.com',
|
||||
'doc': [
|
||||
'https://app.swaggerhub.com/apis-docs/bleu/white-label/3.0.0',
|
||||
],
|
||||
'fees': 'https://bleutrade.com/fees/',
|
||||
},
|
||||
'api': {
|
||||
'v3Public': {
|
||||
'get': [
|
||||
'getassets',
|
||||
'getmarkets',
|
||||
'getticker',
|
||||
'getmarketsummary',
|
||||
'getmarketsummaries',
|
||||
'getorderbook',
|
||||
'getmarkethistory',
|
||||
'getcandles',
|
||||
],
|
||||
},
|
||||
'v3Private': {
|
||||
'get': [
|
||||
'statement',
|
||||
],
|
||||
'post': [
|
||||
'getbalance',
|
||||
'getbalances',
|
||||
'buylimit',
|
||||
'selllimit',
|
||||
'buylimitami',
|
||||
'selllimitami',
|
||||
'buystoplimit',
|
||||
'sellstoplimit',
|
||||
'ordercancel',
|
||||
'getopenorders',
|
||||
'getcloseorders',
|
||||
'getdeposithistory',
|
||||
'getdepositaddress',
|
||||
'getmytransactions',
|
||||
'withdraw',
|
||||
'directtransfer',
|
||||
'getwithdrawhistory',
|
||||
'getlimits',
|
||||
],
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'EPC': 'Epacoin',
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {
|
||||
'ERR_INSUFICIENT_BALANCE': InsufficientFunds,
|
||||
'ERR_LOW_VOLUME': BadRequest,
|
||||
'Invalid form': BadRequest,
|
||||
},
|
||||
'broad': {
|
||||
'Order is not open': InvalidOrder,
|
||||
'Invalid Account / Api KEY / Api Secret': AuthenticationError, // also happens when an invalid nonce is used
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'taker': 0.25 / 100,
|
||||
'maker': 0.25 / 100,
|
||||
},
|
||||
},
|
||||
'options': {
|
||||
'parseOrderStatus': true,
|
||||
},
|
||||
});
|
||||
// undocumented api calls
|
||||
// https://bleutrade.com/api/v3/public/tradingview/symbols?symbol=ETH_BTC
|
||||
// https://bleutrade.com/api/v3/public/tradingview/config
|
||||
// https://bleutrade.com/api/v3/public/tradingview/time
|
||||
// https://bleutrade.com/api/v3/private/getcloseorders?market=ETH_BTC
|
||||
// https://bleutrade.com/config contains the fees
|
||||
}
|
||||
|
||||
async fetchCurrencies (params = {}) {
|
||||
const response = await this.v3PublicGetGetassets (params);
|
||||
const items = response['result'];
|
||||
const result = {};
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
// { Asset: 'USDT',
|
||||
// AssetLong: 'Tether',
|
||||
// MinConfirmation: 4,
|
||||
// WithdrawTxFee: 1,
|
||||
// WithdrawTxFeePercent: 0,
|
||||
// SystemProtocol: 'ETHERC20',
|
||||
// IsActive: true,
|
||||
// InfoMessage: '',
|
||||
// MaintenanceMode: false,
|
||||
// MaintenanceMessage: '',
|
||||
// FormatPrefix: '',
|
||||
// FormatSufix: '',
|
||||
// DecimalSeparator: '.',
|
||||
// ThousandSeparator: ',',
|
||||
// DecimalPlaces: 8,
|
||||
// Currency: 'USDT',
|
||||
// CurrencyLong: 'Tether',
|
||||
// CoinType: 'ETHERC20' }
|
||||
const item = items[i];
|
||||
const id = this.safeString (item, 'Asset');
|
||||
const code = this.safeCurrencyCode (id);
|
||||
result[code] = {
|
||||
'id': id,
|
||||
'code': code,
|
||||
'name': this.safeString (item, 'AssetLong'),
|
||||
'active': this.safeValue (item, 'IsActive') && !this.safeValue (item, 'MaintenanceMode'),
|
||||
'fee': this.safeFloat (item, 'WithdrawTxFee'),
|
||||
'precision': this.safeFloat (item, 'DecimalPlaces'),
|
||||
'info': item,
|
||||
'limits': this.limits,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
// https://github.com/ccxt/ccxt/issues/5668
|
||||
const response = await this.v3PublicGetGetmarkets (params);
|
||||
const result = [];
|
||||
const markets = this.safeValue (response, 'result');
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
// { MarketName: 'LTC_USDT',
|
||||
// MarketAsset: 'LTC',
|
||||
// BaseAsset: 'USDT',
|
||||
// MarketAssetLong: 'Litecoin',
|
||||
// BaseAssetLong: 'Tether',
|
||||
// IsActive: true,
|
||||
// MinTradeSize: 0.0001,
|
||||
// InfoMessage: '',
|
||||
// MarketCurrency: 'LTC',
|
||||
// BaseCurrency: 'USDT',
|
||||
// MarketCurrencyLong: 'Litecoin',
|
||||
// BaseCurrencyLong: 'Tether' }
|
||||
const id = this.safeString (market, 'MarketName');
|
||||
const baseId = this.safeString (market, 'MarketAsset');
|
||||
const quoteId = this.safeString (market, 'BaseAsset');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
};
|
||||
const active = this.safeValue (market, 'IsActive', false);
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'info': market,
|
||||
'precision': precision,
|
||||
'maker': this.fees['trading']['maker'],
|
||||
'taker': this.fees['trading']['taker'],
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'MinTradeSize'),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'market': this.marketId (symbol),
|
||||
'type': 'ALL',
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['depth'] = limit; // 50
|
||||
}
|
||||
const response = await this.v3PublicGetGetorderbook (this.extend (request, params));
|
||||
const orderbook = this.safeValue (response, 'result');
|
||||
if (!orderbook) {
|
||||
throw new ExchangeError (this.id + ' no orderbook data in ' + this.json (response));
|
||||
}
|
||||
return this.parseOrderBook (orderbook, undefined, 'buy', 'sell', 'Rate', 'Quantity');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.v3PublicGetGetmarketsummary (this.extend (request, params));
|
||||
const ticker = this.safeValue (response, 'result', {});
|
||||
return this.parseTicker (ticker, market);
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.v3PublicGetGetmarketsummaries (params);
|
||||
const result = this.safeValue (response, 'result');
|
||||
const tickers = [];
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const ticker = this.parseTicker (result[i]);
|
||||
tickers.push (ticker);
|
||||
}
|
||||
return this.filterByArray (tickers, 'symbol', symbols);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
// { TimeStamp: '2020-01-14 14:32:28',
|
||||
// MarketName: 'LTC_USDT',
|
||||
// MarketAsset: 'LTC',
|
||||
// BaseAsset: 'USDT',
|
||||
// MarketAssetName: 'Litecoin',
|
||||
// BaseAssetName: 'Tether',
|
||||
// PrevDay: 49.2867503,
|
||||
// High: 56.78622664,
|
||||
// Low: 49.27384025,
|
||||
// Last: 53.94,
|
||||
// Average: 51.37509368,
|
||||
// Volume: 1.51282404,
|
||||
// BaseVolume: 77.72147677,
|
||||
// Bid: 53.62070218,
|
||||
// Ask: 53.94,
|
||||
// IsActive: 'true',
|
||||
// InfoMessage: '',
|
||||
// MarketCurrency: 'Litecoin',
|
||||
// BaseCurrency: 'Tether' }
|
||||
const timestamp = this.parse8601 (this.safeString (ticker, 'TimeStamp'));
|
||||
const marketId = this.safeString (ticker, 'MarketName');
|
||||
const symbol = this.safeSymbol (marketId, market, '_');
|
||||
const previous = this.safeFloat (ticker, 'PrevDay');
|
||||
const last = this.safeFloat (ticker, 'Last');
|
||||
let change = undefined;
|
||||
let percentage = undefined;
|
||||
if (last !== undefined) {
|
||||
if (previous !== undefined) {
|
||||
change = last - previous;
|
||||
if (previous > 0) {
|
||||
percentage = (change / previous) * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'High'),
|
||||
'low': this.safeFloat (ticker, 'Low'),
|
||||
'bid': this.safeFloat (ticker, 'Bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'Ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': previous,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': change,
|
||||
'percentage': percentage,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'Volume'),
|
||||
'quoteVolume': this.safeFloat (ticker, 'BaseVolume'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined) {
|
||||
return [
|
||||
this.parse8601 (ohlcv['TimeStamp'] + '+00:00'),
|
||||
this.safeFloat (ohlcv, 'Open'),
|
||||
this.safeFloat (ohlcv, 'High'),
|
||||
this.safeFloat (ohlcv, 'Low'),
|
||||
this.safeFloat (ohlcv, 'Close'),
|
||||
this.safeFloat (ohlcv, 'Volume'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '15m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'period': this.timeframes[timeframe],
|
||||
'market': market['id'],
|
||||
'count': limit,
|
||||
};
|
||||
const response = await this.v3PublicGetGetcandles (this.extend (request, params));
|
||||
const result = this.safeValue (response, 'result', []);
|
||||
return this.parseOHLCVs (result, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
if (type !== 'limit') {
|
||||
// todo: STOP-LIMIT and AMI order types are supported
|
||||
throw new InvalidOrder (this.id + ' allows limit orders only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'rate': this.priceToPrecision (symbol, price),
|
||||
'quantity': this.amountToPrecision (symbol, amount),
|
||||
'tradeType': (side === 'buy') ? '1' : '0',
|
||||
'market': this.marketId (symbol),
|
||||
};
|
||||
let response = undefined;
|
||||
if (side === 'buy') {
|
||||
response = await this.v3PrivatePostBuylimit (this.extend (request, params));
|
||||
} else {
|
||||
response = await this.v3PrivatePostSelllimit (this.extend (request, params));
|
||||
}
|
||||
// { success: true,
|
||||
// message: "",
|
||||
// result: "161105236" },
|
||||
return {
|
||||
'info': response,
|
||||
'id': this.safeString (response, 'result'),
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'orderid': id,
|
||||
};
|
||||
const response = await this.v3PrivatePostOrdercancel (this.extend (request, params));
|
||||
// { success: true, message: '', result: '' }
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
const request = {};
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['market'] = market['id'];
|
||||
}
|
||||
const response = await this.v3PrivatePostGetopenorders (this.extend (request, params));
|
||||
const items = this.safeValue (response, 'result', []);
|
||||
return this.parseOrders (items, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.v3PrivatePostGetbalances (params);
|
||||
const result = { 'info': response };
|
||||
const items = response['result'];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const currencyId = this.safeString (item, 'Asset');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (item, 'Available');
|
||||
account['total'] = this.safeFloat (item, 'Balance');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['market'] = market['id'];
|
||||
}
|
||||
const response = await this.v3PrivatePostGetcloseorders (this.extend (request, params));
|
||||
const orders = this.safeValue (response, 'result', []);
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchTransactionsWithMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this[method] (params);
|
||||
const transactions = this.safeValue (response, 'result', []);
|
||||
return this.parseTransactions (transactions, code, since, limit);
|
||||
}
|
||||
|
||||
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchTransactionsWithMethod ('v3PrivatePostGetdeposithistory', code, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchTransactionsWithMethod ('v3PrivatePostGetwithdrawhistory', code, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'asset': currency['id'],
|
||||
};
|
||||
const response = await this.v3PrivatePostGetdepositaddress (this.extend (request, params));
|
||||
// { success: true,
|
||||
// message: '',
|
||||
// result:
|
||||
// { Asset: 'ETH',
|
||||
// AssetName: 'Ethereum',
|
||||
// DepositAddress: '0x748c5c8jhksjdfhd507d3aa9',
|
||||
// Currency: 'ETH',
|
||||
// CurrencyName: 'Ethereum' } }
|
||||
const item = response['result'];
|
||||
const address = this.safeString (item, 'DepositAddress');
|
||||
return {
|
||||
'currency': code,
|
||||
'address': this.checkAddress (address),
|
||||
// 'tag': tag,
|
||||
'info': item,
|
||||
};
|
||||
}
|
||||
|
||||
parseLedgerEntryType (type) {
|
||||
// deposits don't seem to appear in here
|
||||
const types = {
|
||||
'TRADE': 'trade',
|
||||
'WITHDRAW': 'transaction',
|
||||
};
|
||||
return this.safeString (types, type, type);
|
||||
}
|
||||
|
||||
parseLedgerEntry (item, currency = undefined) {
|
||||
//
|
||||
// trade (both sides)
|
||||
//
|
||||
// {
|
||||
// ID: 109660527,
|
||||
// TimeStamp: '2018-11-14 15:12:57.140776',
|
||||
// Asset: 'ETH',
|
||||
// AssetName: 'Ethereum',
|
||||
// Amount: 0.01,
|
||||
// Type: 'TRADE',
|
||||
// Description: 'Trade +, order id 133111123',
|
||||
// Comments: '',
|
||||
// CoinSymbol: 'ETH',
|
||||
// CoinName: 'Ethereum'
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// ID: 109660526,
|
||||
// TimeStamp: '2018-11-14 15:12:57.140776',
|
||||
// Asset: 'BTC',
|
||||
// AssetName: 'Bitcoin',
|
||||
// Amount: -0.00031776,
|
||||
// Type: 'TRADE',
|
||||
// Description: 'Trade -, order id 133111123, fee -0.00000079',
|
||||
// Comments: '',
|
||||
// CoinSymbol: 'BTC',
|
||||
// CoinName: 'Bitcoin'
|
||||
// }
|
||||
//
|
||||
// withdrawal
|
||||
//
|
||||
// {
|
||||
// ID: 104672316,
|
||||
// TimeStamp: '2018-05-03 08:18:19.031831',
|
||||
// Asset: 'DOGE',
|
||||
// AssetName: 'Dogecoin',
|
||||
// Amount: -61893.87864686,
|
||||
// Type: 'WITHDRAW',
|
||||
// Description: 'Withdraw: 61883.87864686 to address DD8tgehNNyYB2iqVazi2W1paaztgcWXtF6; fee 10.00000000',
|
||||
// Comments: '',
|
||||
// CoinSymbol: 'DOGE',
|
||||
// CoinName: 'Dogecoin'
|
||||
// }
|
||||
//
|
||||
const code = this.safeCurrencyCode (this.safeString (item, 'CoinSymbol'), currency);
|
||||
const description = this.safeString (item, 'Description');
|
||||
const type = this.parseLedgerEntryType (this.safeString (item, 'Type'));
|
||||
let referenceId = undefined;
|
||||
let fee = undefined;
|
||||
const delimiter = (type === 'trade') ? ', ' : '; ';
|
||||
const parts = description.split (delimiter);
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
let part = parts[i];
|
||||
if (part.indexOf ('fee') === 0) {
|
||||
part = part.replace ('fee ', '');
|
||||
let feeCost = parseFloat (part);
|
||||
if (feeCost < 0) {
|
||||
feeCost = -feeCost;
|
||||
}
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': code,
|
||||
};
|
||||
} else if (part.indexOf ('order id') === 0) {
|
||||
referenceId = part.replace ('order id ', '');
|
||||
}
|
||||
//
|
||||
// does not belong to Ledger, related to parseTransaction
|
||||
//
|
||||
// if (part.indexOf ('Withdraw') === 0) {
|
||||
// const details = part.split (' to address ');
|
||||
// if (details.length > 1) {
|
||||
// address = details[1];
|
||||
// }
|
||||
//
|
||||
}
|
||||
const timestamp = this.parse8601 (this.safeString (item, 'TimeStamp'));
|
||||
let amount = this.safeFloat (item, 'Amount');
|
||||
let direction = undefined;
|
||||
if (amount !== undefined) {
|
||||
direction = 'in';
|
||||
if (amount < 0) {
|
||||
direction = 'out';
|
||||
amount = -amount;
|
||||
}
|
||||
}
|
||||
const id = this.safeString (item, 'ID');
|
||||
return {
|
||||
'id': id,
|
||||
'info': item,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'direction': direction,
|
||||
'account': undefined,
|
||||
'referenceId': referenceId,
|
||||
'referenceAccount': undefined,
|
||||
'type': type,
|
||||
'currency': code,
|
||||
'amount': amount,
|
||||
'before': undefined,
|
||||
'after': undefined,
|
||||
'status': 'ok',
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
// only seems to return 100 items and there is no documented way to change page size or offset
|
||||
const request = {
|
||||
};
|
||||
const response = await this.v3PrivatePostGetmytransactions (this.extend (request, params));
|
||||
const items = response['result'];
|
||||
return this.parseLedger (items, code, since, limit);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// fetchClosedOrders
|
||||
//
|
||||
// { OrderID: 89742658,
|
||||
// Exchange: 'DOGE_BTC',
|
||||
// Type: 'BUY',
|
||||
// Quantity: 10000,
|
||||
// QuantityRemaining: 0,
|
||||
// QuantityBaseTraded: 0,
|
||||
// Price: 6.6e-7,
|
||||
// Status: 'OK',
|
||||
// Created: '2018-02-16 08:55:36',
|
||||
// Comments: '' }
|
||||
//
|
||||
// fetchOpenOrders
|
||||
//
|
||||
// { OrderID: 161105302,
|
||||
// Exchange: 'ETH_BTC',
|
||||
// Type: 'SELL',
|
||||
// Quantity: 0.4,
|
||||
// QuantityRemaining: 0.4,
|
||||
// QuantityBaseTraded: 0,
|
||||
// Price: 0.04,
|
||||
// Status: 'OPEN',
|
||||
// Created: '2020-01-22 09:21:27',
|
||||
// Comments: { String: '', Valid: true }
|
||||
const side = this.safeString (order, 'Type').toLowerCase ();
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'Status'));
|
||||
const marketId = this.safeString (order, 'Exchange');
|
||||
const symbol = this.safeSymbol (marketId, market, '_');
|
||||
let timestamp = undefined;
|
||||
if ('Created' in order) {
|
||||
timestamp = this.parse8601 (order['Created'] + '+00:00');
|
||||
}
|
||||
let price = this.safeFloat (order, 'Price');
|
||||
let cost = undefined;
|
||||
const amount = this.safeFloat (order, 'Quantity');
|
||||
const remaining = this.safeFloat (order, 'QuantityRemaining');
|
||||
let filled = undefined;
|
||||
if (amount !== undefined && remaining !== undefined) {
|
||||
filled = amount - remaining;
|
||||
}
|
||||
if (!cost) {
|
||||
if (price && filled) {
|
||||
cost = price * filled;
|
||||
}
|
||||
}
|
||||
if (!price) {
|
||||
if (cost && filled) {
|
||||
price = cost / filled;
|
||||
}
|
||||
}
|
||||
const average = this.safeFloat (order, 'PricePerUnit');
|
||||
const id = this.safeString (order, 'OrderID');
|
||||
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': average,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'fee': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'OK': 'closed',
|
||||
'OPEN': 'open',
|
||||
'CANCELED': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseTransaction (transaction, currency = undefined) {
|
||||
//
|
||||
// deposit:
|
||||
//
|
||||
// { ID: 118698752,
|
||||
// Timestamp: '2020-01-21 11:16:09',
|
||||
// Asset: 'ETH',
|
||||
// Amount: 1,
|
||||
// TransactionID: '',
|
||||
// Status: 'CONFIRMED',
|
||||
// Label: '0x748c5c8228d0c596f4d07f338blah',
|
||||
// Symbol: 'ETH' }
|
||||
//
|
||||
// withdrawal:
|
||||
//
|
||||
// { ID: 689281,
|
||||
// Timestamp: '2019-07-05 13:14:43',
|
||||
// Asset: 'BTC',
|
||||
// Amount: -0.108959,
|
||||
// TransactionID: 'da48d6901fslfjsdjflsdjfls852b87e362cad1',
|
||||
// Status: 'CONFIRMED',
|
||||
// Label: '0.1089590;35wztHPMgrebFvvblah;0.00100000',
|
||||
// Symbol: 'BTC' }
|
||||
//
|
||||
const id = this.safeString (transaction, 'ID');
|
||||
let amount = this.safeFloat (transaction, 'Amount');
|
||||
let type = 'deposit';
|
||||
if (amount < 0) {
|
||||
amount = Math.abs (amount);
|
||||
type = 'withdrawal';
|
||||
}
|
||||
const currencyId = this.safeString (transaction, 'Asset');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const label = this.safeString (transaction, 'Label');
|
||||
const timestamp = this.parse8601 (this.safeString (transaction, 'Timestamp'));
|
||||
let txid = this.safeString (transaction, 'TransactionID');
|
||||
let address = undefined;
|
||||
let feeCost = undefined;
|
||||
const labelParts = label.split (';');
|
||||
if (labelParts.length === 3) {
|
||||
amount = parseFloat (labelParts[0]);
|
||||
address = labelParts[1];
|
||||
feeCost = parseFloat (labelParts[2]);
|
||||
} else {
|
||||
address = label;
|
||||
}
|
||||
let fee = undefined;
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'currency': code,
|
||||
'cost': feeCost,
|
||||
};
|
||||
}
|
||||
let status = 'ok';
|
||||
if (txid === 'CANCELED') {
|
||||
txid = undefined;
|
||||
status = 'canceled';
|
||||
}
|
||||
return {
|
||||
'info': transaction,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'id': id,
|
||||
'currency': code,
|
||||
'amount': amount,
|
||||
'address': address,
|
||||
'tag': undefined,
|
||||
'status': status,
|
||||
'type': type,
|
||||
'updated': undefined,
|
||||
'txid': txid,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.implodeParams (this.urls['api'][api], {
|
||||
'hostname': this.hostname,
|
||||
}) + '/';
|
||||
if (api === 'v3Private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const request = {
|
||||
'apikey': this.apiKey,
|
||||
'nonce': this.nonce (),
|
||||
};
|
||||
url += path + '?' + this.urlencode (this.extend (request, params));
|
||||
const signature = this.hmac (this.encode (url), this.encode (this.secret), 'sha512');
|
||||
headers = { 'apisign': signature };
|
||||
} else {
|
||||
url += path + '?' + this.urlencode (params);
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return; // fallback to default error handler
|
||||
}
|
||||
// examples...
|
||||
// {"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}
|
||||
//
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
+641
@@ -0,0 +1,641 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, InvalidOrder, AuthenticationError, ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class braziliex extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'braziliex',
|
||||
'name': 'Braziliex',
|
||||
'countries': [ 'BR' ],
|
||||
'rateLimit': 1000,
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'createOrder': true,
|
||||
'fetchBalance': true,
|
||||
'fetchCurrencies': 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',
|
||||
'api': 'https://braziliex.com/api/v1',
|
||||
'www': 'https://braziliex.com/',
|
||||
'doc': 'https://braziliex.com/exchange/api.php',
|
||||
'fees': 'https://braziliex.com/exchange/fees.php',
|
||||
'referral': 'https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'currencies',
|
||||
'ticker',
|
||||
'ticker/{market}',
|
||||
'orderbook/{market}',
|
||||
'tradehistory/{market}',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'balance',
|
||||
'complete_balance',
|
||||
'open_orders',
|
||||
'trade_history',
|
||||
'deposit_address',
|
||||
'sell',
|
||||
'buy',
|
||||
'cancel_order',
|
||||
'order_status',
|
||||
],
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'EPC': 'Epacoin',
|
||||
'ABC': 'Anti Bureaucracy Coin',
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.005,
|
||||
'taker': 0.005,
|
||||
},
|
||||
},
|
||||
'precision': {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
},
|
||||
'options': {
|
||||
'fetchCurrencies': {
|
||||
'expires': 1000, // 1 second
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchCurrenciesFromCache (params = {}) {
|
||||
// this method is now redundant
|
||||
// currencies are now fetched before markets
|
||||
const options = this.safeValue (this.options, 'fetchCurrencies', {});
|
||||
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.publicGetCurrencies (params);
|
||||
this.options['fetchCurrencies'] = this.extend (options, {
|
||||
'response': response,
|
||||
'timestamp': now,
|
||||
});
|
||||
}
|
||||
return this.safeValue (this.options['fetchCurrencies'], 'response');
|
||||
}
|
||||
|
||||
async fetchCurrencies (params = {}) {
|
||||
const response = await this.fetchCurrenciesFromCache (params);
|
||||
//
|
||||
// {
|
||||
// brl: {
|
||||
// name: "Real",
|
||||
// withdrawal_txFee: 0.0075,
|
||||
// txWithdrawalFee: 9,
|
||||
// MinWithdrawal: 30,
|
||||
// minConf: 1,
|
||||
// minDeposit: 0,
|
||||
// txDepositFee: 0,
|
||||
// txDepositPercentageFee: 0,
|
||||
// minAmountTradeFIAT: 5,
|
||||
// minAmountTradeBTC: 0.0001,
|
||||
// minAmountTradeUSDT: 0.0001,
|
||||
// decimal: 8,
|
||||
// decimal_withdrawal: 8,
|
||||
// active: 1,
|
||||
// dev_active: 1,
|
||||
// under_maintenance: 0,
|
||||
// order: "010",
|
||||
// is_withdrawal_active: 1,
|
||||
// is_deposit_active: 1,
|
||||
// is_token_erc20: 0,
|
||||
// is_fiat: 1,
|
||||
// gateway: 0,
|
||||
// },
|
||||
// btc: {
|
||||
// name: "Bitcoin",
|
||||
// txWithdrawalMinFee: 0.000125,
|
||||
// txWithdrawalFee: 0.00015625,
|
||||
// MinWithdrawal: 0.0005,
|
||||
// minConf: 1,
|
||||
// minDeposit: 0,
|
||||
// txDepositFee: 0,
|
||||
// txDepositPercentageFee: 0,
|
||||
// minAmountTradeFIAT: 5,
|
||||
// minAmountTradeBTC: 0.0001,
|
||||
// minAmountTradeUSDT: 0.0001,
|
||||
// decimal: 8,
|
||||
// decimal_withdrawal: 8,
|
||||
// active: 1,
|
||||
// dev_active: 1,
|
||||
// under_maintenance: 0,
|
||||
// order: "011",
|
||||
// is_withdrawal_active: 1,
|
||||
// is_deposit_active: 1,
|
||||
// is_token_erc20: 0,
|
||||
// is_fiat: 0,
|
||||
// gateway: 1,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
this.options['currencies'] = {
|
||||
'timestamp': this.milliseconds (),
|
||||
'response': response,
|
||||
};
|
||||
const ids = Object.keys (response);
|
||||
const result = {};
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
const currency = response[id];
|
||||
const precision = this.safeInteger (currency, 'decimal');
|
||||
const code = this.safeCurrencyCode (id);
|
||||
let active = this.safeInteger (currency, 'active') === 1;
|
||||
const maintenance = this.safeInteger (currency, 'under_maintenance');
|
||||
if (maintenance !== 0) {
|
||||
active = false;
|
||||
}
|
||||
const canWithdraw = this.safeInteger (currency, 'is_withdrawal_active') === 1;
|
||||
const canDeposit = this.safeInteger (currency, 'is_deposit_active') === 1;
|
||||
if (!canWithdraw || !canDeposit) {
|
||||
active = false;
|
||||
}
|
||||
result[code] = {
|
||||
'id': id,
|
||||
'code': code,
|
||||
'name': currency['name'],
|
||||
'active': active,
|
||||
'precision': precision,
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'active': canWithdraw,
|
||||
'fee': this.safeFloat (currency, 'txWithdrawalFee'),
|
||||
},
|
||||
'deposit': {
|
||||
'active': canDeposit,
|
||||
'fee': this.safeFloat (currency, 'txDepositFee'),
|
||||
},
|
||||
},
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision),
|
||||
'max': Math.pow (10, precision),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision),
|
||||
'max': Math.pow (10, precision),
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'withdraw': {
|
||||
'min': this.safeFloat (currency, 'MinWithdrawal'),
|
||||
'max': Math.pow (10, precision),
|
||||
},
|
||||
'deposit': {
|
||||
'min': this.safeFloat (currency, 'minDeposit'),
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': currency,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const currencies = await this.fetchCurrenciesFromCache (params);
|
||||
const response = await this.publicGetTicker ();
|
||||
//
|
||||
// {
|
||||
// btc_brl: {
|
||||
// active: 1,
|
||||
// market: 'btc_brl',
|
||||
// last: 14648,
|
||||
// percentChange: -0.95,
|
||||
// baseVolume24: 27.856,
|
||||
// quoteVolume24: 409328.039,
|
||||
// baseVolume: 27.856,
|
||||
// quoteVolume: 409328.039,
|
||||
// highestBid24: 14790,
|
||||
// lowestAsk24: 14450.01,
|
||||
// highestBid: 14450.37,
|
||||
// lowestAsk: 14699.98
|
||||
// },
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
const ids = Object.keys (response);
|
||||
const result = [];
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
const market = response[id];
|
||||
const [ baseId, quoteId ] = id.split ('_');
|
||||
const uppercaseBaseId = baseId.toUpperCase ();
|
||||
const uppercaseQuoteId = quoteId.toUpperCase ();
|
||||
const base = this.safeCurrencyCode (uppercaseBaseId);
|
||||
const quote = this.safeCurrencyCode (uppercaseQuoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const baseCurrency = this.safeValue (currencies, baseId, {});
|
||||
const quoteCurrency = this.safeValue (currencies, quoteId, {});
|
||||
const quoteIsFiat = this.safeInteger (quoteCurrency, 'is_fiat', 0);
|
||||
let minCost = undefined;
|
||||
if (quoteIsFiat) {
|
||||
minCost = this.safeFloat (baseCurrency, 'minAmountTradeFIAT');
|
||||
} else {
|
||||
minCost = this.safeFloat (baseCurrency, 'minAmountTrade' + uppercaseQuoteId);
|
||||
}
|
||||
const isActive = this.safeInteger (market, 'active');
|
||||
const active = (isActive === 1);
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision['amount']),
|
||||
'max': Math.pow (10, precision['amount']),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': Math.pow (10, precision['price']),
|
||||
},
|
||||
'cost': {
|
||||
'min': minCost,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.milliseconds ();
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'highestBid24'),
|
||||
'low': this.safeFloat (ticker, 'lowestAsk24'),
|
||||
'bid': this.safeFloat (ticker, 'highestBid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'lowestAsk'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': this.safeFloat (ticker, 'percentChange'),
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'baseVolume24'),
|
||||
'quoteVolume': this.safeFloat (ticker, 'quoteVolume24'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTickerMarket (this.extend (request, params));
|
||||
return this.parseTicker (response, market);
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetTicker (params);
|
||||
const result = {};
|
||||
const ids = Object.keys (response);
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const marketId = ids[i];
|
||||
const market = this.safeMarket (marketId);
|
||||
const symbol = market['symbol'];
|
||||
result[symbol] = this.parseTicker (response[marketId], market);
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'market': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetOrderbookMarket (this.extend (request, params));
|
||||
return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount');
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString2 (trade, 'date_exec', 'date'));
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const cost = this.safeFloat (trade, 'total');
|
||||
const orderId = this.safeString (trade, 'order_number');
|
||||
const type = 'limit';
|
||||
const side = this.safeString (trade, 'type');
|
||||
const id = this.safeString (trade, '_id');
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': orderId,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTradehistoryMarket (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const balances = await this.privatePostCompleteBalance (params);
|
||||
const result = { 'info': balances };
|
||||
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, 'available');
|
||||
account['total'] = this.safeFloat (balance, 'total');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// {
|
||||
// "order_number":"58ee441d05f8233fadabfb07",
|
||||
// "type":"buy",
|
||||
// "market":"ltc_btc",
|
||||
// "price":"0.01000000",
|
||||
// "amount":"0.00200000",
|
||||
// "total":"0.00002000",
|
||||
// "progress":"1.0000",
|
||||
// "date":"2017-03-12 15:13:33"
|
||||
// }
|
||||
//
|
||||
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'));
|
||||
}
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const cost = this.safeFloat (order, 'total', 0.0);
|
||||
const amount = this.safeFloat (order, 'amount');
|
||||
const filledPercentage = this.safeFloat (order, 'progress');
|
||||
const filled = amount * filledPercentage;
|
||||
const remaining = parseFloat (this.amountToPrecision (symbol, amount - filled));
|
||||
let info = order;
|
||||
if ('info' in info) {
|
||||
info = order['info'];
|
||||
}
|
||||
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,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': undefined,
|
||||
'fee': fee,
|
||||
'info': info,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const method = 'privatePost' + this.capitalize (side);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
// 'price': this.priceToPrecision (symbol, price),
|
||||
// 'amount': this.amountToPrecision (symbol, amount),
|
||||
'price': price,
|
||||
'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));
|
||||
}
|
||||
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': this.safeStringLower (parts, 0),
|
||||
'market': parts[0].toLowerCase (),
|
||||
'amount': this.safeString (amountParts, 1),
|
||||
'price': this.safeString (priceParts, 1),
|
||||
'total': this.safeString (totalParts, 1),
|
||||
'fee': {
|
||||
'cost': this.safeFloat (feeParts, 1),
|
||||
'currency': this.safeString (feeParts, 2),
|
||||
},
|
||||
'progress': '0.0',
|
||||
'info': response,
|
||||
}, market);
|
||||
return order;
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'order_number': id,
|
||||
'market': market['id'],
|
||||
};
|
||||
return await this.privatePostCancelOrder (this.extend (request, params));
|
||||
}
|
||||
|
||||
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_number': id,
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.privatePostOrderStatus (this.extend (request, params));
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.privatePostOpenOrders (this.extend (request, params));
|
||||
const orders = this.safeValue (response, 'order_open', []);
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.privatePostTradeHistory (this.extend (request, params));
|
||||
const trades = this.safeValue (response, 'trade_history', []);
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
};
|
||||
const response = await this.privatePostDepositAddress (this.extend (request, params));
|
||||
const address = this.safeString (response, 'deposit_address');
|
||||
this.checkAddress (address);
|
||||
const tag = this.safeString (response, 'payment_id');
|
||||
return {
|
||||
'currency': code,
|
||||
'address': address,
|
||||
'tag': tag,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + api;
|
||||
let query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
url += '/' + this.implodeParams (path, params);
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
query = this.extend ({
|
||||
'command': path,
|
||||
'nonce': this.nonce (),
|
||||
}, query);
|
||||
body = this.urlencode (query);
|
||||
const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512');
|
||||
headers = {
|
||||
'Content-type': 'application/x-www-form-urlencoded',
|
||||
'Key': this.apiKey,
|
||||
'Sign': signature,
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if ((typeof response === 'string') && (response.length < 1)) {
|
||||
throw new ExchangeError (this.id + ' returned empty response');
|
||||
}
|
||||
if ('success' in response) {
|
||||
const success = this.safeInteger (response, 'success');
|
||||
if (success === 0) {
|
||||
const message = this.safeString (response, 'message');
|
||||
if (message === 'Invalid APIKey') {
|
||||
throw new AuthenticationError (message);
|
||||
}
|
||||
throw new ExchangeError (message);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+502
@@ -0,0 +1,502 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, DDoSProtection, InvalidOrder, InsufficientFunds } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class btcalpha extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'btcalpha',
|
||||
'name': 'BTC-Alpha',
|
||||
'countries': [ 'US' ],
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'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',
|
||||
'5m': '5',
|
||||
'15m': '15',
|
||||
'30m': '30',
|
||||
'1h': '60',
|
||||
'4h': '240',
|
||||
'1d': 'D',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/42625213-dabaa5da-85cf-11e8-8f99-aa8f8f7699f0.jpg',
|
||||
'api': 'https://btc-alpha.com/api',
|
||||
'www': 'https://btc-alpha.com',
|
||||
'doc': 'https://btc-alpha.github.io/api-docs',
|
||||
'fees': 'https://btc-alpha.com/fees/',
|
||||
'referral': 'https://btc-alpha.com/?r=123788',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'currencies/',
|
||||
'pairs/',
|
||||
'orderbook/{pair_name}/',
|
||||
'exchanges/',
|
||||
'charts/{pair}/{type}/chart/',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'wallets/',
|
||||
'orders/own/',
|
||||
'order/{id}/',
|
||||
'exchanges/own/',
|
||||
'deposits/',
|
||||
'withdraws/',
|
||||
],
|
||||
'post': [
|
||||
'order/',
|
||||
'order-cancel/',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.2 / 100,
|
||||
'taker': 0.2 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'BTC': 0.00135,
|
||||
'LTC': 0.0035,
|
||||
'XMR': 0.018,
|
||||
'ZEC': 0.002,
|
||||
'ETH': 0.01,
|
||||
'ETC': 0.01,
|
||||
'SIB': 1.5,
|
||||
'CCRB': 4,
|
||||
'PZM': 0.05,
|
||||
'ITI': 0.05,
|
||||
'DCY': 5,
|
||||
'R': 5,
|
||||
'ATB': 0.05,
|
||||
'BRIA': 0.05,
|
||||
'KZC': 0.05,
|
||||
'HWC': 1,
|
||||
'SPA': 1,
|
||||
'SMS': 0.001,
|
||||
'REC': 0.01,
|
||||
'SUP': 1,
|
||||
'BQ': 100,
|
||||
'GDS': 0.1,
|
||||
'EVN': 300,
|
||||
'TRKC': 0.01,
|
||||
'UNI': 1,
|
||||
'STN': 1,
|
||||
'BCH': undefined,
|
||||
'QBIC': 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'CBC': 'Cashbery',
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {},
|
||||
'broad': {
|
||||
'Out of balance': InsufficientFunds, // {"date":1570599531.4814300537,"error":"Out of balance -9.99243661 BTC"}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetPairs (params);
|
||||
const result = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const market = response[i];
|
||||
const id = this.safeString (market, 'name');
|
||||
const baseId = this.safeString (market, 'currency1');
|
||||
const quoteId = this.safeString (market, 'currency2');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': this.safeInteger (market, 'price_precision'),
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'active': true,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'minimum_order_size'),
|
||||
'max': this.safeFloat (market, 'maximum_order_size'),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': Math.pow (10, precision['price']),
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': market,
|
||||
'baseId': undefined,
|
||||
'quoteId': undefined,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'pair_name': this.marketId (symbol),
|
||||
};
|
||||
if (limit) {
|
||||
request['limit_sell'] = limit;
|
||||
request['limit_buy'] = limit;
|
||||
}
|
||||
const response = await this.publicGetOrderbookPairName (this.extend (request, params));
|
||||
return this.parseOrderBook (response, undefined, 'buy', 'sell', 'price', 'amount');
|
||||
}
|
||||
|
||||
parseBidsAsks (bidasks, priceKey = 0, amountKey = 1) {
|
||||
const result = [];
|
||||
for (let i = 0; i < bidasks.length; i++) {
|
||||
const bidask = bidasks[i];
|
||||
if (bidask) {
|
||||
result.push (this.parseBidAsk (bidask, priceKey, amountKey));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
market = this.safeValue (this.marketsById, trade['pair']);
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeTimestamp (trade, 'timestamp');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = parseFloat (this.costToPrecision (symbol, price * amount));
|
||||
}
|
||||
}
|
||||
const id = this.safeString2 (trade, 'id', 'tid');
|
||||
const side = this.safeString2 (trade, 'my_side', 'side');
|
||||
const orderId = this.safeString (trade, 'o_id');
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': orderId,
|
||||
'type': 'limit',
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
const request = {};
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['pair'] = market['id'];
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const trades = await this.publicGetExchanges (this.extend (request, params));
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
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'),
|
||||
this.safeFloat (ohlcv, 'high'),
|
||||
this.safeFloat (ohlcv, 'low'),
|
||||
this.safeFloat (ohlcv, 'close'),
|
||||
this.safeFloat (ohlcv, 'volume'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
'type': this.timeframes[timeframe],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
if (since !== undefined) {
|
||||
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);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetWallets (params);
|
||||
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['used'] = this.safeFloat (balance, 'reserve');
|
||||
account['total'] = this.safeFloat (balance, 'balance');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'1': 'open',
|
||||
'2': 'canceled',
|
||||
'3': 'closed',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
market = this.safeValue (this.marketsById, order['pair']);
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeTimestamp (order, 'date');
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const amount = this.safeFloat (order, 'amount');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const id = this.safeString2 (order, 'oid', 'id');
|
||||
let trades = this.safeValue (order, 'trades', []);
|
||||
trades = this.parseTrades (trades, market);
|
||||
const side = this.safeString2 (order, 'my_side', 'type');
|
||||
let filled = undefined;
|
||||
const numTrades = trades.length;
|
||||
if (numTrades > 0) {
|
||||
filled = 0.0;
|
||||
for (let i = 0; i < numTrades; i++) {
|
||||
filled = this.sum (filled, trades[i]['amount']);
|
||||
}
|
||||
}
|
||||
let remaining = undefined;
|
||||
if ((amount !== undefined) && (amount > 0) && (filled !== undefined)) {
|
||||
remaining = Math.max (0, amount - filled);
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': undefined,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': trades,
|
||||
'fee': undefined,
|
||||
'info': order,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
'type': side,
|
||||
'amount': amount,
|
||||
'price': this.priceToPrecision (symbol, price),
|
||||
};
|
||||
const response = await this.privatePostOrder (this.extend (request, params));
|
||||
if (!response['success']) {
|
||||
throw new InvalidOrder (this.id + ' ' + this.json (response));
|
||||
}
|
||||
const order = this.parseOrder (response, market);
|
||||
amount = (order['amount'] > 0) ? order['amount'] : amount;
|
||||
return this.extend (order, {
|
||||
'amount': amount,
|
||||
});
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'order': id,
|
||||
};
|
||||
const response = await this.privatePostOrderCancel (this.extend (request, params));
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const order = await this.privateGetOrderId (this.extend (request, params));
|
||||
return this.parseOrder (order);
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['pair'] = market['id'];
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const orders = await this.privateGetOrdersOwn (this.extend (request, params));
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const request = {
|
||||
'status': '1',
|
||||
};
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const request = {
|
||||
'status': '3',
|
||||
};
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
if (symbol !== undefined) {
|
||||
const market = this.market (symbol);
|
||||
request['pair'] = market['id'];
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const trades = await this.privateGetExchangesOwn (this.extend (request, params));
|
||||
return this.parseTrades (trades, undefined, since, limit);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const query = this.urlencode (this.keysort (this.omit (params, this.extractParams (path))));
|
||||
let url = this.urls['api'] + '/';
|
||||
if (path !== 'charts/{pair}/{type}/chart/') {
|
||||
url += 'v1/';
|
||||
}
|
||||
url += this.implodeParams (path, params);
|
||||
headers = { 'Accept': 'application/json' };
|
||||
if (api === 'public') {
|
||||
if (query.length) {
|
||||
url += '?' + query;
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
let payload = this.apiKey;
|
||||
if (method === 'POST') {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
body = query;
|
||||
payload += body;
|
||||
} else if (query.length) {
|
||||
url += '?' + query;
|
||||
}
|
||||
headers['X-KEY'] = this.apiKey;
|
||||
headers['X-SIGN'] = this.hmac (this.encode (payload), this.encode (this.secret));
|
||||
headers['X-NONCE'] = this.nonce ().toString ();
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return; // fallback to default error handler
|
||||
}
|
||||
//
|
||||
// {"date":1570599531.4814300537,"error":"Out of balance -9.99243661 BTC"}
|
||||
//
|
||||
const error = this.safeString (response, 'error');
|
||||
const feedback = this.id + ' ' + body;
|
||||
if (error !== undefined) {
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback);
|
||||
this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback);
|
||||
}
|
||||
if (code === 401 || code === 403) {
|
||||
throw new AuthenticationError (feedback);
|
||||
} else if (code === 429) {
|
||||
throw new DDoSProtection (feedback);
|
||||
}
|
||||
if (code < 400) {
|
||||
return;
|
||||
}
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
};
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, InsufficientFunds, InvalidOrder, AuthenticationError, PermissionDenied, InvalidNonce, OrderNotFound, DDoSProtection } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class btcbox extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'btcbox',
|
||||
'name': 'BtcBox',
|
||||
'countries': [ 'JP' ],
|
||||
'rateLimit': 1000,
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': false,
|
||||
'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/51840849/87327317-98c55400-c53c-11ea-9a11-81f7d951cc74.jpg',
|
||||
'api': 'https://www.btcbox.co.jp/api',
|
||||
'www': 'https://www.btcbox.co.jp/',
|
||||
'doc': 'https://blog.btcbox.jp/en/archives/8762',
|
||||
'fees': 'https://support.btcbox.co.jp/hc/en-us/articles/360001235694-Fees-introduction',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'depth',
|
||||
'orders',
|
||||
'ticker',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'balance',
|
||||
'trade_add',
|
||||
'trade_cancel',
|
||||
'trade_list',
|
||||
'trade_view',
|
||||
'wallet',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/JPY': { 'id': 'btc', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy', 'taker': 0.05 / 100, 'maker': 0.05 / 100 },
|
||||
'ETH/JPY': { 'id': 'eth', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 },
|
||||
'LTC/JPY': { 'id': 'ltc', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 },
|
||||
'BCH/JPY': { 'id': 'bch', 'symbol': 'BCH/JPY', 'base': 'BCH', 'quote': 'JPY', 'baseId': 'bch', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 },
|
||||
},
|
||||
'exceptions': {
|
||||
'104': AuthenticationError,
|
||||
'105': PermissionDenied,
|
||||
'106': InvalidNonce,
|
||||
'107': InvalidOrder, // price should be an integer
|
||||
'200': InsufficientFunds,
|
||||
'201': InvalidOrder, // amount too small
|
||||
'202': InvalidOrder, // price should be [0 : 1000000]
|
||||
'203': OrderNotFound,
|
||||
'401': OrderNotFound, // cancel canceled, closed or non-existent order
|
||||
'402': DDoSProtection,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostBalance (params);
|
||||
const result = { 'info': response };
|
||||
const codes = Object.keys (this.currencies);
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const currency = this.currency (code);
|
||||
const currencyId = currency['id'];
|
||||
const free = currencyId + '_balance';
|
||||
if (free in response) {
|
||||
const account = this.account ();
|
||||
const used = currencyId + '_lock';
|
||||
account['free'] = this.safeFloat (response, free);
|
||||
account['used'] = this.safeFloat (response, used);
|
||||
result[code] = account;
|
||||
}
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {};
|
||||
const numSymbols = this.symbols.length;
|
||||
if (numSymbols > 1) {
|
||||
request['coin'] = market['baseId'];
|
||||
}
|
||||
const response = await this.publicGetDepth (this.extend (request, params));
|
||||
return this.parseOrderBook (response);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.milliseconds ();
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'vol'),
|
||||
'quoteVolume': this.safeFloat (ticker, 'volume'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {};
|
||||
const numSymbols = this.symbols.length;
|
||||
if (numSymbols > 1) {
|
||||
request['coin'] = market['baseId'];
|
||||
}
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
return this.parseTicker (response, market);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'date');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
const type = undefined;
|
||||
const side = this.safeString (trade, 'type');
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'order': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {};
|
||||
const numSymbols = this.symbols.length;
|
||||
if (numSymbols > 1) {
|
||||
request['coin'] = market['baseId'];
|
||||
}
|
||||
const response = await this.publicGetOrders (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'amount': amount,
|
||||
'price': price,
|
||||
'type': side,
|
||||
'coin': market['baseId'],
|
||||
};
|
||||
const response = await this.privatePostTradeAdd (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "result":true,
|
||||
// "id":"11"
|
||||
// }
|
||||
//
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
// a special case for btcbox – default symbol is BTC/JPY
|
||||
if (symbol === undefined) {
|
||||
symbol = 'BTC/JPY';
|
||||
}
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': id,
|
||||
'coin': market['baseId'],
|
||||
};
|
||||
const response = await this.privatePostTradeCancel (this.extend (request, params));
|
||||
//
|
||||
// {"result":true, "id":"11"}
|
||||
//
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
// TODO: complete list
|
||||
'part': 'open', // partially or not at all executed
|
||||
'all': 'closed', // fully executed
|
||||
'cancelled': 'canceled',
|
||||
'closed': 'closed', // never encountered, seems to be bug in the doc
|
||||
'no': 'closed', // not clarified in the docs...
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// {
|
||||
// "id":11,
|
||||
// "datetime":"2014-10-21 10:47:20",
|
||||
// "type":"sell",
|
||||
// "price":42000,
|
||||
// "amount_original":1.2,
|
||||
// "amount_outstanding":1.2,
|
||||
// "status":"closed",
|
||||
// "trades":[]
|
||||
// }
|
||||
//
|
||||
const id = this.safeString (order, 'id');
|
||||
const datetimeString = this.safeString (order, 'datetime');
|
||||
let timestamp = undefined;
|
||||
if (datetimeString !== undefined) {
|
||||
timestamp = this.parse8601 (order['datetime'] + '+09:00'); // Tokyo time
|
||||
}
|
||||
const amount = this.safeFloat (order, 'amount_original');
|
||||
const remaining = this.safeFloat (order, 'amount_outstanding');
|
||||
let filled = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (remaining !== undefined) {
|
||||
filled = amount - remaining;
|
||||
}
|
||||
}
|
||||
const price = this.safeFloat (order, 'price');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (filled !== undefined) {
|
||||
cost = filled * price;
|
||||
}
|
||||
}
|
||||
// status is set by fetchOrder method only
|
||||
let status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
// fetchOrders do not return status, use heuristic
|
||||
if (status === undefined) {
|
||||
if (remaining !== undefined && remaining === 0) {
|
||||
status = 'closed';
|
||||
}
|
||||
}
|
||||
const trades = undefined; // todo: this.parseTrades (order['trades']);
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const side = this.safeString (order, 'type');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'amount': amount,
|
||||
'remaining': remaining,
|
||||
'filled': filled,
|
||||
'side': side,
|
||||
'type': undefined,
|
||||
'timeInForce': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'trades': trades,
|
||||
'fee': undefined,
|
||||
'info': order,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
// a special case for btcbox – default symbol is BTC/JPY
|
||||
if (symbol === undefined) {
|
||||
symbol = 'BTC/JPY';
|
||||
}
|
||||
const market = this.market (symbol);
|
||||
const request = this.extend ({
|
||||
'id': id,
|
||||
'coin': market['baseId'],
|
||||
}, params);
|
||||
const response = await this.privatePostTradeView (this.extend (request, params));
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
async fetchOrdersByType (type, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
// a special case for btcbox – default symbol is BTC/JPY
|
||||
if (symbol === undefined) {
|
||||
symbol = 'BTC/JPY';
|
||||
}
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'type': type, // 'open' or 'all'
|
||||
'coin': market['baseId'],
|
||||
};
|
||||
const response = await this.privatePostTradeList (this.extend (request, params));
|
||||
const orders = this.parseOrders (response, market, since, limit);
|
||||
// status (open/closed/canceled) is undefined
|
||||
// btcbox does not return status, but we know it's 'open' as we queried for open orders
|
||||
if (type === 'open') {
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
orders[i]['status'] = 'open';
|
||||
}
|
||||
}
|
||||
return orders;
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchOrdersByType ('all', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchOrdersByType ('open', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.version + '/' + path;
|
||||
if (api === 'public') {
|
||||
if (Object.keys (params).length) {
|
||||
url += '?' + this.urlencode (params);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
const query = this.extend ({
|
||||
'key': this.apiKey,
|
||||
'nonce': nonce,
|
||||
}, params);
|
||||
const request = this.urlencode (query);
|
||||
const secret = this.hash (this.encode (this.secret));
|
||||
query['signature'] = this.hmac (this.encode (request), this.encode (secret));
|
||||
body = this.urlencode (query);
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return; // resort to defaultErrorHandler
|
||||
}
|
||||
// typical error response: {"result":false,"code":"401"}
|
||||
if (httpCode >= 400) {
|
||||
return; // resort to defaultErrorHandler
|
||||
}
|
||||
const result = this.safeValue (response, 'result');
|
||||
if (result === undefined || result === true) {
|
||||
return; // either public API (no error codes expected) or success
|
||||
}
|
||||
const code = this.safeValue (response, 'code');
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwExactlyMatchedException (this.exceptions, code, feedback);
|
||||
throw new ExchangeError (feedback); // unknown message
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if (typeof response === 'string') {
|
||||
// sometimes the exchange returns whitespace prepended to json
|
||||
response = this.strip (response);
|
||||
if (!this.isJsonEncodedObject (response)) {
|
||||
throw new ExchangeError (this.id + ' ' + response);
|
||||
}
|
||||
response = JSON.parse (response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+1055
File diff suppressed because it is too large.
Load diff
+393
@@ -0,0 +1,393 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class btctradeua extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'btctradeua',
|
||||
'name': 'BTC Trade UA',
|
||||
'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': {
|
||||
'referral': 'https://btc-trade.com.ua/registration/22689',
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27941483-79fc7350-62d9-11e7-9f61-ac47f28fcd96.jpg',
|
||||
'api': 'https://btc-trade.com.ua/api',
|
||||
'www': 'https://btc-trade.com.ua',
|
||||
'doc': 'https://docs.google.com/document/d/1ocYA0yMy_RXd561sfG3qEPZ80kyll36HUxvCRe5GbhE/edit',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'deals/{symbol}',
|
||||
'trades/sell/{symbol}',
|
||||
'trades/buy/{symbol}',
|
||||
'japan_stat/high/{symbol}',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'auth',
|
||||
'ask/{symbol}',
|
||||
'balance',
|
||||
'bid/{symbol}',
|
||||
'buy/{symbol}',
|
||||
'my_orders/{symbol}',
|
||||
'order/status/{id}',
|
||||
'remove/order/{id}',
|
||||
'sell/{symbol}',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BCH/UAH': { 'id': 'bch_uah', 'symbol': 'BCH/UAH', 'base': 'BCH', 'quote': 'UAH', 'baseId': 'bch', 'quoteId': 'uah' },
|
||||
'BTC/UAH': { 'id': 'btc_uah', 'symbol': 'BTC/UAH', 'base': 'BTC', 'quote': 'UAH', 'baseId': 'btc', 'quoteId': 'uah', 'precision': { 'price': 1 }, 'limits': { 'amount': { 'min': 0.0000000001 }}},
|
||||
'DASH/BTC': { 'id': 'dash_btc', 'symbol': 'DASH/BTC', 'base': 'DASH', 'quote': 'BTC', 'baseId': 'dash', 'quoteId': 'btc' },
|
||||
'DASH/UAH': { 'id': 'dash_uah', 'symbol': 'DASH/UAH', 'base': 'DASH', 'quote': 'UAH', 'baseId': 'dash', 'quoteId': 'uah' },
|
||||
'DOGE/BTC': { 'id': 'doge_btc', 'symbol': 'DOGE/BTC', 'base': 'DOGE', 'quote': 'BTC', 'baseId': 'doge', 'quoteId': 'btc' },
|
||||
'DOGE/UAH': { 'id': 'doge_uah', 'symbol': 'DOGE/UAH', 'base': 'DOGE', 'quote': 'UAH', 'baseId': 'doge', 'quoteId': 'uah' },
|
||||
'ETH/UAH': { 'id': 'eth_uah', 'symbol': 'ETH/UAH', 'base': 'ETH', 'quote': 'UAH', 'baseId': 'eth', 'quoteId': 'uah' },
|
||||
'ITI/UAH': { 'id': 'iti_uah', 'symbol': 'ITI/UAH', 'base': 'ITI', 'quote': 'UAH', 'baseId': 'iti', 'quoteId': 'uah' },
|
||||
'KRB/UAH': { 'id': 'krb_uah', 'symbol': 'KRB/UAH', 'base': 'KRB', 'quote': 'UAH', 'baseId': 'krb', 'quoteId': 'uah' },
|
||||
'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' },
|
||||
'LTC/UAH': { 'id': 'ltc_uah', 'symbol': 'LTC/UAH', 'base': 'LTC', 'quote': 'UAH', 'baseId': 'ltc', 'quoteId': 'uah' },
|
||||
'NVC/BTC': { 'id': 'nvc_btc', 'symbol': 'NVC/BTC', 'base': 'NVC', 'quote': 'BTC', 'baseId': 'nvc', 'quoteId': 'btc' },
|
||||
'NVC/UAH': { 'id': 'nvc_uah', 'symbol': 'NVC/UAH', 'base': 'NVC', 'quote': 'UAH', 'baseId': 'nvc', 'quoteId': 'uah' },
|
||||
'PPC/BTC': { 'id': 'ppc_btc', 'symbol': 'PPC/BTC', 'base': 'PPC', 'quote': 'BTC', 'baseId': 'ppc', 'quoteId': 'btc' },
|
||||
'SIB/UAH': { 'id': 'sib_uah', 'symbol': 'SIB/UAH', 'base': 'SIB', 'quote': 'UAH', 'baseId': 'sib', 'quoteId': 'uah' },
|
||||
'XMR/UAH': { 'id': 'xmr_uah', 'symbol': 'XMR/UAH', 'base': 'XMR', 'quote': 'UAH', 'baseId': 'xmr', 'quoteId': 'uah' },
|
||||
'ZEC/UAH': { 'id': 'zec_uah', 'symbol': 'ZEC/UAH', 'base': 'ZEC', 'quote': 'UAH', 'baseId': 'zec', 'quoteId': 'uah' },
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.1 / 100,
|
||||
'taker': 0.1 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'BTC': 0.0006,
|
||||
'LTC': 0.01,
|
||||
'NVC': 0.01,
|
||||
'DOGE': 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async signIn (params = {}) {
|
||||
return await this.privatePostAuth (params);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostBalance (params);
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'accounts');
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[i];
|
||||
const currencyId = this.safeString (balance, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
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 = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const bids = await this.publicGetTradesBuySymbol (this.extend (request, params));
|
||||
const asks = await this.publicGetTradesSellSymbol (this.extend (request, params));
|
||||
const orderbook = {
|
||||
'bids': [],
|
||||
'asks': [],
|
||||
};
|
||||
if (bids) {
|
||||
if ('list' in bids) {
|
||||
orderbook['bids'] = bids['list'];
|
||||
}
|
||||
}
|
||||
if (asks) {
|
||||
if ('list' in asks) {
|
||||
orderbook['asks'] = asks['list'];
|
||||
}
|
||||
}
|
||||
return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'currency_trade');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetJapanStatHighSymbol (this.extend (request, params));
|
||||
const ticker = this.safeValue (response, 'trades');
|
||||
const timestamp = this.milliseconds ();
|
||||
const result = {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': undefined,
|
||||
'bidVolume': undefined,
|
||||
'ask': undefined,
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': undefined,
|
||||
'last': undefined,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': undefined,
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
const tickerLength = ticker.length;
|
||||
if (tickerLength > 0) {
|
||||
const start = Math.max (tickerLength - 48, 0);
|
||||
for (let i = start; i < ticker.length; i++) {
|
||||
const candle = ticker[i];
|
||||
if (result['open'] === undefined) {
|
||||
result['open'] = candle[1];
|
||||
}
|
||||
if ((result['high'] === undefined) || (result['high'] < candle[2])) {
|
||||
result['high'] = candle[2];
|
||||
}
|
||||
if ((result['low'] === undefined) || (result['low'] > candle[3])) {
|
||||
result['low'] = candle[3];
|
||||
}
|
||||
if (result['baseVolume'] === undefined) {
|
||||
result['baseVolume'] = -candle[5];
|
||||
} else {
|
||||
result['baseVolume'] -= candle[5];
|
||||
}
|
||||
}
|
||||
const last = tickerLength - 1;
|
||||
result['last'] = ticker[last][4];
|
||||
result['close'] = result['last'];
|
||||
result['baseVolume'] = -1 * result['baseVolume'];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
convertCyrillicMonthNameToString (cyrillic) {
|
||||
const months = {
|
||||
'января': '01',
|
||||
'февраля': '02',
|
||||
'марта': '03',
|
||||
'апреля': '04',
|
||||
'мая': '05',
|
||||
'июня': '06',
|
||||
'июля': '07',
|
||||
'августа': '08',
|
||||
'сентября': '09',
|
||||
'октября': '10',
|
||||
'ноября': '11',
|
||||
'декабря': '12',
|
||||
};
|
||||
return this.safeString (months, cyrillic);
|
||||
}
|
||||
|
||||
parseCyrillicDatetime (cyrillic) {
|
||||
const parts = cyrillic.split (' ');
|
||||
let day = parts[0];
|
||||
const month = this.convertCyrillicMonthNameToString (parts[1]);
|
||||
if (!month) {
|
||||
throw new ExchangeError (this.id + ' parseTrade() undefined month name: ' + cyrillic);
|
||||
}
|
||||
const year = parts[2];
|
||||
let hms = parts[4];
|
||||
const hmsLength = hms.length;
|
||||
if (hmsLength === 7) {
|
||||
hms = '0' + hms;
|
||||
}
|
||||
if (day.length === 1) {
|
||||
day = '0' + day;
|
||||
}
|
||||
const ymd = [ year, month, day ].join ('-');
|
||||
const ymdhms = ymd + 'T' + hms;
|
||||
const timestamp = this.parse8601 (ymdhms);
|
||||
// server reports local time, adjust to UTC
|
||||
let md = [ month, day ].join ('');
|
||||
md = parseInt (md);
|
||||
// a special case for DST
|
||||
// subtract 2 hours during winter
|
||||
if (md < 325 || md > 1028) {
|
||||
return timestamp - 7200000;
|
||||
}
|
||||
// subtract 3 hours during summer
|
||||
return timestamp - 10800000;
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.parseCyrillicDatetime (this.safeString (trade, 'pub_date'));
|
||||
const id = this.safeString (trade, 'id');
|
||||
const type = 'limit';
|
||||
const side = this.safeString (trade, 'type');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amnt_trade');
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'order': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const response = await this.publicGetDealsSymbol (this.extend (request, params));
|
||||
// they report each trade twice (once for both of the two sides of the fill)
|
||||
// deduplicate trades for that reason
|
||||
const trades = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
if (response[i]['id'] % 2) {
|
||||
trades.push (response[i]);
|
||||
}
|
||||
}
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
if (type === 'market') {
|
||||
throw new ExchangeError (this.id + ' allows limit orders only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const method = 'privatePost' + this.capitalize (side) + 'Id';
|
||||
const request = {
|
||||
'count': amount,
|
||||
'currency1': market['quoteId'],
|
||||
'currency': market['baseId'],
|
||||
'price': price,
|
||||
};
|
||||
return this[method] (this.extend (request, params));
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
return await this.privatePostRemoveOrderId (this.extend (request, params));
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const timestamp = this.milliseconds ();
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'id': this.safeString (order, 'id'),
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp, // until they fix their timestamp
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'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'),
|
||||
'trades': undefined,
|
||||
'info': order,
|
||||
'cost': undefined,
|
||||
'average': undefined,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const response = await this.privatePostMyOrdersSymbol (this.extend (request, params));
|
||||
const orders = this.safeValue (response, 'your_open_orders');
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += this.implodeParams (path, query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
body = this.urlencode (this.extend ({
|
||||
'out_order_id': nonce,
|
||||
'nonce': nonce,
|
||||
}, query));
|
||||
const auth = body + this.secret;
|
||||
headers = {
|
||||
'public-key': this.apiKey,
|
||||
'api-sign': this.hash (this.encode (auth), 'sha256'),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class btcturk extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'btcturk',
|
||||
'name': 'BTCTurk',
|
||||
'countries': [ 'TR' ], // Turkey
|
||||
'rateLimit': 1000,
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': 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/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',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'ohlcdata', // ?last=COUNT
|
||||
'orderbook',
|
||||
'ticker',
|
||||
'trades', // ?last=COUNT (max 50)
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'balance',
|
||||
'openOrders',
|
||||
'userTransactions', // ?offset=0&limit=25&sort=asc
|
||||
],
|
||||
'post': [
|
||||
'exchange',
|
||||
'cancelOrder',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.002 * 1.18,
|
||||
'taker': 0.003 * 1.18,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetTicker (params);
|
||||
const result = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const market = response[i];
|
||||
const id = this.safeString (market, 'pair');
|
||||
let baseId = id.slice (0, 3);
|
||||
let quoteId = id.slice (3, 6);
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
baseId = baseId.toLowerCase ();
|
||||
quoteId = quoteId.toLowerCase ();
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
};
|
||||
const active = true;
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'info': market,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision['amount']),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetBalance (params);
|
||||
const result = { 'info': response };
|
||||
const codes = Object.keys (this.currencies);
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const currency = this.currencies[code];
|
||||
const free = currency['id'] + '_available';
|
||||
const total = currency['id'] + '_balance';
|
||||
const used = currency['id'] + '_reserved';
|
||||
if (free in response) {
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (response, free);
|
||||
account['total'] = this.safeFloat (response, total);
|
||||
account['used'] = this.safeFloat (response, used);
|
||||
result[code] = account;
|
||||
}
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pairSymbol': market['id'],
|
||||
};
|
||||
const response = await this.publicGetOrderbook (this.extend (request, params));
|
||||
const timestamp = this.safeTimestamp (response, 'timestamp');
|
||||
return this.parseOrderBook (response, timestamp);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeTimestamp (ticker, 'timestamp');
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': this.safeFloat (ticker, 'open'),
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': this.safeFloat (ticker, 'average'),
|
||||
'baseVolume': this.safeFloat (ticker, 'volume'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const tickers = await this.publicGetTicker (params);
|
||||
const result = {};
|
||||
for (let i = 0; i < tickers.length; i++) {
|
||||
const ticker = tickers[i];
|
||||
const marketId = this.safeString (ticker, 'pair');
|
||||
let symbol = marketId;
|
||||
let market = undefined;
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[symbol];
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
result[symbol] = this.parseTicker (ticker, market);
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const tickers = await this.fetchTickers (params);
|
||||
return this.safeValue2 (tickers, market['id'], symbol);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'date');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': undefined,
|
||||
'order': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
// let maxCount = 50;
|
||||
const request = {
|
||||
'pairSymbol': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTrades (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined) {
|
||||
return [
|
||||
this.parse8601 (this.safeString (ohlcv, 'Time')),
|
||||
this.safeFloat (ohlcv, 'Open'),
|
||||
this.safeFloat (ohlcv, 'High'),
|
||||
this.safeFloat (ohlcv, 'Low'),
|
||||
this.safeFloat (ohlcv, 'Close'),
|
||||
this.safeFloat (ohlcv, 'Volume'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1d', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {};
|
||||
if (limit !== undefined) {
|
||||
request['last'] = limit;
|
||||
}
|
||||
const response = await this.publicGetOhlcdata (this.extend (request, params));
|
||||
return this.parseOHLCVs (response, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'PairSymbol': this.marketId (symbol),
|
||||
'OrderType': (side === 'buy') ? 0 : 1,
|
||||
'OrderMethod': (type === 'market') ? 1 : 0,
|
||||
};
|
||||
if (type === 'market') {
|
||||
if (!('Total' in params)) {
|
||||
throw new ExchangeError (this.id + ' createOrder requires the "Total" extra parameter for market orders (amount and price are both ignored)');
|
||||
}
|
||||
} else {
|
||||
request['Price'] = price;
|
||||
request['Amount'] = amount;
|
||||
}
|
||||
const response = await this.privatePostExchange (this.extend (request, params));
|
||||
const id = this.safeString (response, 'id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
return await this.privatePostCancelOrder (this.extend (request, params));
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
if (this.id === 'btctrader') {
|
||||
throw new ExchangeError (this.id + ' is an abstract base API for BTCExchange, BTCTurk');
|
||||
}
|
||||
let url = this.urls['api'] + '/' + path;
|
||||
if (api === 'public') {
|
||||
if (Object.keys (params).length) {
|
||||
url += '?' + this.urlencode (params);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
body = this.urlencode (params);
|
||||
const secret = this.base64ToBinary (this.secret);
|
||||
const auth = this.apiKey + nonce;
|
||||
headers = {
|
||||
'X-PCK': this.apiKey,
|
||||
'X-Stamp': nonce,
|
||||
'X-Signature': this.hmac (this.encode (auth), secret, 'sha256', 'base64'),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+788
@@ -0,0 +1,788 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { AddressPending, AuthenticationError, ExchangeError, NotSupported, PermissionDenied, ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class buda extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'buda',
|
||||
'name': 'Buda',
|
||||
'countries': [ 'AR', 'CL', 'CO', 'PE' ],
|
||||
'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,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/47380619-8a029200-d706-11e8-91e0-8a391fe48de3.jpg',
|
||||
'api': 'https://www.buda.com/api',
|
||||
'www': 'https://www.buda.com',
|
||||
'doc': 'https://api.buda.com',
|
||||
'fees': 'https://www.buda.com/comisiones',
|
||||
},
|
||||
'status': {
|
||||
'status': 'error',
|
||||
'updated': undefined,
|
||||
'eta': undefined,
|
||||
'url': undefined,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'pairs',
|
||||
'markets',
|
||||
'currencies',
|
||||
'markets/{market}',
|
||||
'markets/{market}/ticker',
|
||||
'markets/{market}/volume',
|
||||
'markets/{market}/order_book',
|
||||
'markets/{market}/trades',
|
||||
'currencies/{currency}/fees/deposit',
|
||||
'currencies/{currency}/fees/withdrawal',
|
||||
'tv/history',
|
||||
],
|
||||
'post': [
|
||||
'markets/{market}/quotations',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'balances',
|
||||
'balances/{currency}',
|
||||
'currencies/{currency}/balances',
|
||||
'orders',
|
||||
'orders/{id}',
|
||||
'markets/{market}/orders',
|
||||
'deposits',
|
||||
'currencies/{currency}/deposits',
|
||||
'withdrawals',
|
||||
'currencies/{currency}/withdrawals',
|
||||
'currencies/{currency}/receive_addresses',
|
||||
'currencies/{currency}/receive_addresses/{id}',
|
||||
],
|
||||
'post': [
|
||||
'markets/{market}/orders',
|
||||
'currencies/{currency}/deposits',
|
||||
'currencies/{currency}/withdrawals',
|
||||
'currencies/{currency}/simulated_withdrawals',
|
||||
'currencies/{currency}/receive_addresses',
|
||||
],
|
||||
'put': [
|
||||
'orders/{id}',
|
||||
],
|
||||
},
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': '1',
|
||||
'5m': '5',
|
||||
'30m': '30',
|
||||
'1h': '60',
|
||||
'2h': '120',
|
||||
'1d': 'D',
|
||||
'1w': 'W',
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'percentage': true,
|
||||
'taker': 0.008, // 0.8%
|
||||
'maker': 0.004, // 0.4%
|
||||
'tiers': {
|
||||
'taker': [
|
||||
[0, 0.008], // 0.8%
|
||||
[2000, 0.007], // 0.7%
|
||||
[20000, 0.006], // 0.6%
|
||||
[100000, 0.005], // 0.5%
|
||||
[500000, 0.004], // 0.4%
|
||||
[2500000, 0.003], // 0.3%
|
||||
[12500000, 0.002], // 0.2%
|
||||
],
|
||||
'maker': [
|
||||
[0, 0.004], // 0.4%
|
||||
[2000, 0.0035], // 0.35%
|
||||
[20000, 0.003], // 0.3%
|
||||
[100000, 0.0025], // 0.25%
|
||||
[500000, 0.002], // 0.2%
|
||||
[2500000, 0.0015], // 0.15%
|
||||
[12500000, 0.001], // 0.1%
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'not_authorized': AuthenticationError, // { message: 'Invalid credentials', code: 'not_authorized' }
|
||||
'forbidden': PermissionDenied, // { message: 'You dont have access to this resource', code: 'forbidden' }
|
||||
'invalid_record': ExchangeError, // { message: 'Validation Failed', code: 'invalid_record', errors: [] }
|
||||
'not_found': ExchangeError, // { message: 'Not found', code: 'not_found' }
|
||||
'parameter_missing': ExchangeError, // { message: 'Parameter missing', code: 'parameter_missing' }
|
||||
'bad_parameter': ExchangeError, // { message: 'Bad Parameter format', code: 'bad_parameter' }
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchCurrencyInfo (currency, currencies = undefined) {
|
||||
if (!currencies) {
|
||||
const response = await this.publicGetCurrencies ();
|
||||
currencies = this.safeValue (response, 'currencies');
|
||||
}
|
||||
for (let i = 0; i < currencies.length; i++) {
|
||||
const currencyInfo = currencies[i];
|
||||
if (currencyInfo['id'] === currency) {
|
||||
return currencyInfo;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const marketsResponse = await this.publicGetMarkets (params);
|
||||
const markets = this.safeValue (marketsResponse, 'markets');
|
||||
const currenciesResponse = await this.publicGetCurrencies ();
|
||||
const currencies = this.safeValue (currenciesResponse, 'currencies');
|
||||
const result = [];
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const id = this.safeString (market, 'id');
|
||||
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 baseInfo = await this.fetchCurrencyInfo (baseId, currencies);
|
||||
const quoteInfo = await this.fetchCurrencyInfo (quoteId, currencies);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': baseInfo['input_decimals'],
|
||||
'price': quoteInfo['input_decimals'],
|
||||
};
|
||||
const limits = {
|
||||
'amount': {
|
||||
'min': parseFloat (market['minimum_order_amount'][0]),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': undefined,
|
||||
},
|
||||
};
|
||||
limits['cost'] = {
|
||||
'min': limits['amount']['min'] * limits['price']['min'],
|
||||
'max': undefined,
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': true,
|
||||
'precision': precision,
|
||||
'limits': limits,
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchCurrencies (params = {}) {
|
||||
const response = await this.publicGetCurrencies ();
|
||||
const currencies = response['currencies'];
|
||||
const result = {};
|
||||
for (let i = 0; i < currencies.length; i++) {
|
||||
const currency = currencies[i];
|
||||
if (!currency['managed']) {
|
||||
continue;
|
||||
}
|
||||
const id = this.safeString (currency, 'id');
|
||||
const code = this.safeCurrencyCode (id);
|
||||
const precision = this.safeFloat (currency, 'input_decimals');
|
||||
const minimum = Math.pow (10, -precision);
|
||||
result[code] = {
|
||||
'id': id,
|
||||
'code': code,
|
||||
'info': currency,
|
||||
'name': undefined,
|
||||
'active': true,
|
||||
'fee': undefined,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': minimum,
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': minimum,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'deposit': {
|
||||
'min': parseFloat (currency['deposit_minimum'][0]),
|
||||
'max': undefined,
|
||||
},
|
||||
'withdraw': {
|
||||
'min': parseFloat (currency['withdrawal_minimum'][0]),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchFundingFees (codes = undefined, params = {}) {
|
||||
// by default it will try load withdrawal fees of all currencies (with separate requests)
|
||||
// however if you define codes = [ 'ETH', 'BTC' ] in args it will only load those
|
||||
await this.loadMarkets ();
|
||||
const withdrawFees = {};
|
||||
const depositFees = {};
|
||||
const info = {};
|
||||
if (codes === undefined) {
|
||||
codes = Object.keys (this.currencies);
|
||||
}
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const currency = this.currency (code);
|
||||
const request = { 'currency': currency['id'] };
|
||||
const withdrawResponse = await this.publicGetCurrenciesCurrencyFeesWithdrawal (request);
|
||||
const depositResponse = await this.publicGetCurrenciesCurrencyFeesDeposit (request);
|
||||
withdrawFees[code] = this.parseFundingFee (withdrawResponse['fee']);
|
||||
depositFees[code] = this.parseFundingFee (depositResponse['fee']);
|
||||
info[code] = {
|
||||
'withdraw': withdrawResponse,
|
||||
'deposit': depositResponse,
|
||||
};
|
||||
}
|
||||
return {
|
||||
'withdraw': withdrawFees,
|
||||
'deposit': depositFees,
|
||||
'info': info,
|
||||
};
|
||||
}
|
||||
|
||||
parseFundingFee (fee, type = undefined) {
|
||||
if (type === undefined) {
|
||||
type = fee['name'];
|
||||
}
|
||||
if (type === 'withdrawal') {
|
||||
type = 'withdraw';
|
||||
}
|
||||
return {
|
||||
'type': type,
|
||||
'currency': fee['base'][1],
|
||||
'rate': fee['percent'],
|
||||
'cost': parseFloat (fee['base'][0]),
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketsMarketTicker (this.extend (request, params));
|
||||
const ticker = this.safeValue (response, 'ticker');
|
||||
return this.parseTicker (ticker, market);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.milliseconds ();
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const last = parseFloat (ticker['last_price'][0]);
|
||||
const percentage = parseFloat (ticker['price_variation_24h']);
|
||||
const open = parseFloat (this.priceToPrecision (symbol, last / (percentage + 1)));
|
||||
const change = last - open;
|
||||
const average = this.sum (last, open) / 2;
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': parseFloat (ticker['max_bid'][0]),
|
||||
'bidVolume': undefined,
|
||||
'ask': parseFloat (ticker['min_ask'][0]),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': open,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': open,
|
||||
'change': change,
|
||||
'percentage': percentage * 100,
|
||||
'average': average,
|
||||
'baseVolume': parseFloat (ticker['volume'][0]),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
// the since argument works backwards – returns trades up to the specified timestamp
|
||||
// therefore not implemented here
|
||||
// the method is still available for users to be able to traverse backwards in time
|
||||
// by using the timestamp from the first received trade upon each iteration
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit; // 50 max
|
||||
}
|
||||
const response = await this.publicGetMarketsMarketTrades (this.extend (request, params));
|
||||
//
|
||||
// { trades: { market_id: "ETH-BTC",
|
||||
// timestamp: null,
|
||||
// last_timestamp: "1536901277302",
|
||||
// entries: [ [ "1540077456791", "0.0063767", "0.03", "sell", 479842 ],
|
||||
// [ "1539916642772", "0.01888263", "0.03019563", "sell", 479438 ],
|
||||
// [ "1539834081787", "0.023718648", "0.031001", "sell", 479069 ],
|
||||
// ... ]
|
||||
//
|
||||
return this.parseTrades (response['trades']['entries'], market, since, limit);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
//
|
||||
// fetchTrades (public)
|
||||
// [ "1540077456791", "0.0063767", "0.03", "sell", 479842 ]
|
||||
//
|
||||
let timestamp = undefined;
|
||||
let side = undefined;
|
||||
const type = undefined;
|
||||
let price = undefined;
|
||||
let amount = undefined;
|
||||
let id = undefined;
|
||||
const order = undefined;
|
||||
const fee = undefined;
|
||||
let symbol = undefined;
|
||||
let cost = undefined;
|
||||
if (market) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
if (Array.isArray (trade)) {
|
||||
timestamp = parseInt (trade[0]);
|
||||
price = parseFloat (trade[1]);
|
||||
amount = parseFloat (trade[2]);
|
||||
cost = price * amount;
|
||||
side = trade[3];
|
||||
id = trade[4].toString ();
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'order': order,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketsMarketOrderBook (this.extend (request, params));
|
||||
const orderbook = this.safeValue (response, 'order_book');
|
||||
return this.parseOrderBook (orderbook);
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
if (since === undefined) {
|
||||
since = this.milliseconds () - 86400000;
|
||||
}
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'resolution': this.timeframes[timeframe],
|
||||
'from': since / 1000,
|
||||
'to': this.seconds (),
|
||||
};
|
||||
const response = await this.publicGetTvHistory (this.extend (request, params));
|
||||
return this.parseTradingViewOHLCV (response, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetBalances (params);
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'balances');
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[i];
|
||||
const currencyId = this.safeString (balance, 'id');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = parseFloat (balance['available_amount'][0]);
|
||||
account['total'] = parseFloat (balance['amount'][0]);
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': parseInt (id),
|
||||
};
|
||||
const response = await this.privateGetOrdersId (this.extend (request, params));
|
||||
const order = this.safeValue (response, 'order');
|
||||
return this.parseOrder (order);
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
'per': limit,
|
||||
};
|
||||
const response = await this.privateGetMarketsMarketOrders (this.extend (request, params));
|
||||
const orders = this.safeValue (response, 'orders');
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const request = {
|
||||
'state': 'pending',
|
||||
};
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const request = {
|
||||
'state': 'traded',
|
||||
};
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
side = (side === 'buy') ? 'Bid' : 'Ask';
|
||||
const request = {
|
||||
'market': this.marketId (symbol),
|
||||
'price_type': type,
|
||||
'type': side,
|
||||
'amount': this.amountToPrecision (symbol, amount),
|
||||
};
|
||||
if (type === 'limit') {
|
||||
request['limit'] = this.priceToPrecision (symbol, price);
|
||||
}
|
||||
const response = await this.privatePostMarketsMarketOrders (this.extend (request, params));
|
||||
const order = this.safeValue (response, 'order');
|
||||
return this.parseOrder (order);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': parseInt (id),
|
||||
'state': 'canceling',
|
||||
};
|
||||
const response = await this.privatePutOrdersId (this.extend (request, params));
|
||||
const order = this.safeValue (response, 'order');
|
||||
return this.parseOrder (order);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'traded': 'closed',
|
||||
'received': 'open',
|
||||
'canceling': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const id = this.safeString (order, 'id');
|
||||
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
|
||||
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'));
|
||||
const amount = parseFloat (order['original_amount'][0]);
|
||||
const remaining = parseFloat (order['amount'][0]);
|
||||
const filled = parseFloat (order['traded_amount'][0]);
|
||||
const cost = parseFloat (order['total_exchanged'][0]);
|
||||
let price = this.safeFloat (order, 'limit');
|
||||
if (price !== undefined) {
|
||||
price = parseFloat (price[0]);
|
||||
}
|
||||
if (cost > 0 && filled > 0) {
|
||||
price = this.priceToPrecision (symbol, cost / filled);
|
||||
}
|
||||
const fee = {
|
||||
'cost': parseFloat (order['paid_fee'][0]),
|
||||
'currency': order['paid_fee'][1],
|
||||
};
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': undefined,
|
||||
'fee': fee,
|
||||
'info': order,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
isFiat (code) {
|
||||
const fiats = {
|
||||
'ARS': true,
|
||||
'CLP': true,
|
||||
'COP': true,
|
||||
'PEN': true,
|
||||
};
|
||||
return this.safeValue (fiats, code, false);
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
if (this.isFiat (code)) {
|
||||
throw new NotSupported (this.id + ' fetchDepositAddress() for fiat ' + code + ' is not supported');
|
||||
}
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
};
|
||||
const response = await this.privateGetCurrenciesCurrencyReceiveAddresses (this.extend (request, params));
|
||||
const receiveAddresses = this.safeValue (response, 'receive_addresses');
|
||||
const addressPool = [];
|
||||
for (let i = 1; i < receiveAddresses.length; i++) {
|
||||
const receiveAddress = receiveAddresses[i];
|
||||
if (receiveAddress['ready']) {
|
||||
const address = receiveAddress['address'];
|
||||
this.checkAddress (address);
|
||||
addressPool.push (address);
|
||||
}
|
||||
}
|
||||
const addressPoolLength = addressPool.length;
|
||||
if (addressPoolLength < 1) {
|
||||
throw new AddressPending (this.id + ': there are no addresses ready for receiving ' + code + ', retry again later)');
|
||||
}
|
||||
const address = addressPool[0];
|
||||
return {
|
||||
'currency': code,
|
||||
'address': address,
|
||||
'tag': undefined,
|
||||
'info': receiveAddresses,
|
||||
};
|
||||
}
|
||||
|
||||
async createDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
if (this.isFiat (code)) {
|
||||
throw new NotSupported (this.id + ': fiat fetchDepositAddress() for ' + code + ' is not supported');
|
||||
}
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
};
|
||||
const response = await this.privatePostCurrenciesCurrencyReceiveAddresses (this.extend (request, params));
|
||||
const address = this.safeString (response['receive_address'], 'address'); // the creation is async and returns a null address, returns only the id
|
||||
return {
|
||||
'currency': code,
|
||||
'address': address,
|
||||
'tag': undefined,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
parseTransactionStatus (status) {
|
||||
const statuses = {
|
||||
'rejected': 'failed',
|
||||
'confirmed': 'ok',
|
||||
'anulled': 'canceled',
|
||||
'retained': 'canceled',
|
||||
'pending_confirmation': 'pending',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseTransaction (transaction, currency = undefined) {
|
||||
const id = this.safeString (transaction, 'id');
|
||||
const timestamp = this.parse8601 (this.safeString (transaction, 'created_at'));
|
||||
const currencyId = this.safeString (transaction, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const amount = parseFloat (transaction['amount'][0]);
|
||||
const fee = parseFloat (transaction['fee'][0]);
|
||||
const feeCurrency = transaction['fee'][1];
|
||||
const status = this.parseTransactionStatus (this.safeString (transaction, 'state'));
|
||||
const type = ('deposit_data' in transaction) ? 'deposit' : 'withdrawal';
|
||||
const data = this.safeValue (transaction, type + '_data', {});
|
||||
const address = this.safeValue (data, 'target_address');
|
||||
const txid = this.safeString (data, 'tx_hash');
|
||||
const updated = this.parse8601 (this.safeString (data, 'updated_at'));
|
||||
return {
|
||||
'info': transaction,
|
||||
'id': id,
|
||||
'txid': txid,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'address': address,
|
||||
'type': type,
|
||||
'amount': amount,
|
||||
'currency': code,
|
||||
'status': status,
|
||||
'updated': updated,
|
||||
'fee': {
|
||||
'cost': fee,
|
||||
'rate': feeCurrency,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (code === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ': fetchDeposits() requires a currency code argument');
|
||||
}
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
'per': limit,
|
||||
};
|
||||
const response = await this.privateGetCurrenciesCurrencyDeposits (this.extend (request, params));
|
||||
const deposits = this.safeValue (response, 'deposits');
|
||||
return this.parseTransactions (deposits, currency, since, limit);
|
||||
}
|
||||
|
||||
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (code === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ': fetchDeposits() requires a currency code argument');
|
||||
}
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
'per': limit,
|
||||
};
|
||||
const response = await this.privateGetCurrenciesCurrencyWithdrawals (this.extend (request, params));
|
||||
const withdrawals = this.safeValue (response, 'withdrawals');
|
||||
return this.parseTransactions (withdrawals, currency, since, limit);
|
||||
}
|
||||
|
||||
async withdraw (code, amount, address, tag = undefined, params = {}) {
|
||||
this.checkAddress (address);
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
'amount': amount,
|
||||
'withdrawal_data': {
|
||||
'target_address': address,
|
||||
},
|
||||
};
|
||||
const response = await this.privatePostCurrenciesCurrencyWithdrawals (this.extend (request, params));
|
||||
const withdrawal = this.safeValue (response, 'withdrawal');
|
||||
return this.parseTransaction (withdrawal);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.microseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let request = this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (Object.keys (query).length) {
|
||||
if (method === 'GET') {
|
||||
request += '?' + this.urlencode (query);
|
||||
} else {
|
||||
body = this.json (query);
|
||||
}
|
||||
}
|
||||
const url = this.urls['api'] + '/' + this.version + '/' + request;
|
||||
if (api === 'private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
const components = [ method, '/api/' + this.version + '/' + request ];
|
||||
if (body) {
|
||||
const base64Body = this.stringToBase64 (body);
|
||||
components.push (this.decode (base64Body));
|
||||
}
|
||||
components.push (nonce);
|
||||
const message = components.join (' ');
|
||||
const signature = this.hmac (this.encode (message), this.encode (this.secret), 'sha384');
|
||||
headers = {
|
||||
'X-SBTC-APIKEY': this.apiKey,
|
||||
'X-SBTC-SIGNATURE': signature,
|
||||
'X-SBTC-NONCE': nonce,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return; // fallback to default error handler
|
||||
}
|
||||
if (code >= 400) {
|
||||
const errorCode = this.safeString (response, 'code');
|
||||
const message = this.safeString (response, 'message', body);
|
||||
const feedback = this.id + ' ' + message;
|
||||
if (errorCode !== undefined) {
|
||||
this.throwExactlyMatchedException (this.exceptions, errorCode, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+1205
File diff suppressed because it is too large.
Load diff
+2229
File diff suppressed because it is too large.
Load diff
+1430
File diff suppressed because it is too large.
Load diff
+1348
File diff suppressed because it is too large.
Load diff
+32
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const foxbit = require ('./foxbit.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class chilebit extends foxbit {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'chilebit',
|
||||
'name': 'ChileBit',
|
||||
'countries': [ 'CL' ],
|
||||
'has': {
|
||||
'CORS': false,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27991414-1298f0d8-647f-11e7-9c40-d56409266336.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.blinktrade.com/api',
|
||||
'private': 'https://api.blinktrade.com/tapi',
|
||||
},
|
||||
'www': 'https://chilebit.net',
|
||||
'doc': 'https://blinktrade.com/docs',
|
||||
},
|
||||
'options': {
|
||||
'brokerId': '9', // https://blinktrade.com/docs/#brokers
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+1200
File diff suppressed because it is too large.
Load diff
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const coinbasepro = require ('./coinbasepro.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinbaseprime extends coinbasepro {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinbaseprime',
|
||||
'name': 'Coinbase Prime',
|
||||
'pro': true,
|
||||
'urls': {
|
||||
'test': {
|
||||
'public': 'https://api-public.sandbox.prime.coinbase.com',
|
||||
'private': 'https://api-public.sandbox.prime.coinbase.com',
|
||||
},
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/44539184-29f26e00-a70c-11e8-868f-e907fc236a7c.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.prime.coinbase.com',
|
||||
'private': 'https://api.prime.coinbase.com',
|
||||
},
|
||||
'www': 'https://prime.coinbase.com',
|
||||
'doc': 'https://docs.prime.coinbase.com',
|
||||
'fees': 'https://support.prime.coinbase.com/customer/en/portal/articles/2945629-fees?b_id=17475',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
Generated
Vendored
+1086
File diff suppressed because it is too large.
Load diff
+430
@@ -0,0 +1,430 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { BadSymbol, ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coincheck extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coincheck',
|
||||
'name': 'coincheck',
|
||||
'countries': [ 'JP', 'ID' ],
|
||||
'rateLimit': 1500,
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': false,
|
||||
'createOrder': true,
|
||||
'fetchBalance': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTrades': true,
|
||||
},
|
||||
'urls': {
|
||||
'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',
|
||||
'fees': [
|
||||
'https://coincheck.com/exchange/fee',
|
||||
'https://coincheck.com/info/fee',
|
||||
],
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'exchange/orders/rate',
|
||||
'order_books',
|
||||
'rate/{pair}',
|
||||
'ticker',
|
||||
'trades',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'accounts',
|
||||
'accounts/balance',
|
||||
'accounts/leverage_balance',
|
||||
'bank_accounts',
|
||||
'deposit_money',
|
||||
'exchange/orders/opens',
|
||||
'exchange/orders/transactions',
|
||||
'exchange/orders/transactions_pagination',
|
||||
'exchange/leverage/positions',
|
||||
'lending/borrows/matches',
|
||||
'send_money',
|
||||
'withdraws',
|
||||
],
|
||||
'post': [
|
||||
'bank_accounts',
|
||||
'deposit_money/{id}/fast',
|
||||
'exchange/orders',
|
||||
'exchange/transfers/to_leverage',
|
||||
'exchange/transfers/from_leverage',
|
||||
'lending/borrows',
|
||||
'lending/borrows/{id}/repay',
|
||||
'send_money',
|
||||
'withdraws',
|
||||
],
|
||||
'delete': [
|
||||
'bank_accounts/{id}',
|
||||
'exchange/orders/{id}',
|
||||
'withdraws/{id}',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/JPY': { 'id': 'btc_jpy', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy' }, // the only real pair
|
||||
// 'ETH/JPY': { 'id': 'eth_jpy', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy' },
|
||||
// 'ETC/JPY': { 'id': 'etc_jpy', 'symbol': 'ETC/JPY', 'base': 'ETC', 'quote': 'JPY', 'baseId': 'etc', 'quoteId': 'jpy' },
|
||||
// 'DAO/JPY': { 'id': 'dao_jpy', 'symbol': 'DAO/JPY', 'base': 'DAO', 'quote': 'JPY', 'baseId': 'dao', 'quoteId': 'jpy' },
|
||||
// 'LSK/JPY': { 'id': 'lsk_jpy', 'symbol': 'LSK/JPY', 'base': 'LSK', 'quote': 'JPY', 'baseId': 'lsk', 'quoteId': 'jpy' },
|
||||
// 'FCT/JPY': { 'id': 'fct_jpy', 'symbol': 'FCT/JPY', 'base': 'FCT', 'quote': 'JPY', 'baseId': 'fct', 'quoteId': 'jpy' },
|
||||
// 'XMR/JPY': { 'id': 'xmr_jpy', 'symbol': 'XMR/JPY', 'base': 'XMR', 'quote': 'JPY', 'baseId': 'xmr', 'quoteId': 'jpy' },
|
||||
// 'REP/JPY': { 'id': 'rep_jpy', 'symbol': 'REP/JPY', 'base': 'REP', 'quote': 'JPY', 'baseId': 'rep', 'quoteId': 'jpy' },
|
||||
// 'XRP/JPY': { 'id': 'xrp_jpy', 'symbol': 'XRP/JPY', 'base': 'XRP', 'quote': 'JPY', 'baseId': 'xrp', 'quoteId': 'jpy' },
|
||||
// 'ZEC/JPY': { 'id': 'zec_jpy', 'symbol': 'ZEC/JPY', 'base': 'ZEC', 'quote': 'JPY', 'baseId': 'zec', 'quoteId': 'jpy' },
|
||||
// 'XEM/JPY': { 'id': 'xem_jpy', 'symbol': 'XEM/JPY', 'base': 'XEM', 'quote': 'JPY', 'baseId': 'xem', 'quoteId': 'jpy' },
|
||||
// 'LTC/JPY': { 'id': 'ltc_jpy', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy' },
|
||||
// 'DASH/JPY': { 'id': 'dash_jpy', 'symbol': 'DASH/JPY', 'base': 'DASH', 'quote': 'JPY', 'baseId': 'dash', 'quoteId': 'jpy' },
|
||||
// 'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc' },
|
||||
// 'ETC/BTC': { 'id': 'etc_btc', 'symbol': 'ETC/BTC', 'base': 'ETC', 'quote': 'BTC', 'baseId': 'etc', 'quoteId': 'btc' },
|
||||
// 'LSK/BTC': { 'id': 'lsk_btc', 'symbol': 'LSK/BTC', 'base': 'LSK', 'quote': 'BTC', 'baseId': 'lsk', 'quoteId': 'btc' },
|
||||
// 'FCT/BTC': { 'id': 'fct_btc', 'symbol': 'FCT/BTC', 'base': 'FCT', 'quote': 'BTC', 'baseId': 'fct', 'quoteId': 'btc' },
|
||||
// 'XMR/BTC': { 'id': 'xmr_btc', 'symbol': 'XMR/BTC', 'base': 'XMR', 'quote': 'BTC', 'baseId': 'xmr', 'quoteId': 'btc' },
|
||||
// 'REP/BTC': { 'id': 'rep_btc', 'symbol': 'REP/BTC', 'base': 'REP', 'quote': 'BTC', 'baseId': 'rep', 'quoteId': 'btc' },
|
||||
// 'XRP/BTC': { 'id': 'xrp_btc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc' },
|
||||
// 'ZEC/BTC': { 'id': 'zec_btc', 'symbol': 'ZEC/BTC', 'base': 'ZEC', 'quote': 'BTC', 'baseId': 'zec', 'quoteId': 'btc' },
|
||||
// 'XEM/BTC': { 'id': 'xem_btc', 'symbol': 'XEM/BTC', 'base': 'XEM', 'quote': 'BTC', 'baseId': 'xem', 'quoteId': 'btc' },
|
||||
// 'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' },
|
||||
// 'DASH/BTC': { 'id': 'dash_btc', 'symbol': 'DASH/BTC', 'base': 'DASH', 'quote': 'BTC', 'baseId': 'dash', 'quoteId': 'btc' },
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'maker': 0,
|
||||
'taker': 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const balances = await this.privateGetAccountsBalance (params);
|
||||
const result = { 'info': balances };
|
||||
const codes = Object.keys (this.currencies);
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const currencyId = this.currencyId (code);
|
||||
if (currencyId in balances) {
|
||||
const account = this.account ();
|
||||
const reserved = currencyId + '_reserved';
|
||||
account['free'] = this.safeFloat (balances, currencyId);
|
||||
account['used'] = this.safeFloat (balances, reserved);
|
||||
result[code] = account;
|
||||
}
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
// Only BTC/JPY is meaningful
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const response = await this.privateGetExchangeOrdersOpens (params);
|
||||
const rawOrders = this.safeValue (response, 'orders', []);
|
||||
const parsedOrders = this.parseOrders (rawOrders, market, since, limit);
|
||||
const result = [];
|
||||
for (let i = 0; i < parsedOrders.length; i++) {
|
||||
result.push (this.extend (parsedOrders[i], { 'status': 'open' }));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// fetchOpenOrders
|
||||
//
|
||||
// { id: 202835,
|
||||
// order_type: "buy",
|
||||
// rate: 26890,
|
||||
// pair: "btc_jpy",
|
||||
// pending_amount: "0.5527",
|
||||
// pending_market_buy_amount: null,
|
||||
// stop_loss_rate: null,
|
||||
// created_at: "2015-01-10T05:55:38.000Z" }
|
||||
//
|
||||
// todo: add formats for fetchOrder, fetchClosedOrders here
|
||||
//
|
||||
const id = this.safeString (order, 'id');
|
||||
const side = this.safeString (order, 'order_type');
|
||||
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
|
||||
const amount = this.safeFloat (order, 'pending_amount');
|
||||
const remaining = this.safeFloat (order, 'pending_amount');
|
||||
const price = this.safeFloat (order, 'rate');
|
||||
let filled = undefined;
|
||||
let cost = undefined;
|
||||
if (remaining !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
filled = Math.max (amount - remaining, 0);
|
||||
if (price !== undefined) {
|
||||
cost = filled * price;
|
||||
}
|
||||
}
|
||||
}
|
||||
const status = undefined;
|
||||
const marketId = this.safeString (order, 'pair');
|
||||
const symbol = this.safeSymbol (marketId, market, '_');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'amount': amount,
|
||||
'remaining': remaining,
|
||||
'filled': filled,
|
||||
'side': side,
|
||||
'type': undefined,
|
||||
'timeInForce': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
'info': order,
|
||||
'average': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
if (symbol !== 'BTC/JPY') {
|
||||
throw new BadSymbol (this.id + ' fetchOrderBook () supports BTC/JPY only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetOrderBooks (params);
|
||||
return this.parseOrderBook (response);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
if (symbol !== 'BTC/JPY') {
|
||||
throw new BadSymbol (this.id + ' fetchTicker () supports BTC/JPY only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const ticker = await this.publicGetTicker (params);
|
||||
const timestamp = this.safeTimestamp (ticker, 'timestamp');
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'volume'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (trade, 'created_at'));
|
||||
const id = this.safeString (trade, 'id');
|
||||
const price = this.safeFloat (trade, 'rate');
|
||||
const marketId = this.safeString (trade, 'pair');
|
||||
market = this.safeValue (this.markets_by_id, marketId, market);
|
||||
let symbol = undefined;
|
||||
let baseId = undefined;
|
||||
let quoteId = undefined;
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
baseId = market['baseId'];
|
||||
quoteId = market['quoteId'];
|
||||
symbol = market['symbol'];
|
||||
} else {
|
||||
const ids = marketId.split ('_');
|
||||
baseId = ids[0];
|
||||
quoteId = ids[1];
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if (symbol === undefined) {
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
}
|
||||
let takerOrMaker = undefined;
|
||||
let amount = undefined;
|
||||
let cost = undefined;
|
||||
let side = undefined;
|
||||
let fee = undefined;
|
||||
let orderId = undefined;
|
||||
if ('liquidity' in trade) {
|
||||
if (this.safeString (trade, 'liquidity') === 'T') {
|
||||
takerOrMaker = 'taker';
|
||||
} else if (this.safeString (trade, 'liquidity') === 'M') {
|
||||
takerOrMaker = 'maker';
|
||||
}
|
||||
const funds = this.safeValue (trade, 'funds', {});
|
||||
amount = this.safeFloat (funds, baseId);
|
||||
cost = this.safeFloat (funds, quoteId);
|
||||
fee = {
|
||||
'currency': this.safeString (trade, 'fee_currency'),
|
||||
'cost': this.safeFloat (trade, 'fee'),
|
||||
};
|
||||
side = this.safeString (trade, 'side');
|
||||
orderId = this.safeString (trade, 'order_id');
|
||||
} else {
|
||||
amount = this.safeFloat (trade, 'amount');
|
||||
side = this.safeString (trade, 'order_type');
|
||||
}
|
||||
if (cost === undefined) {
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'order': orderId,
|
||||
'takerOrMaker': takerOrMaker,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const response = await this.privateGetExchangeOrdersTransactions (this.extend ({}, params));
|
||||
const transactions = this.safeValue (response, 'transactions', []);
|
||||
return this.parseTrades (transactions, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol !== 'BTC/JPY') {
|
||||
throw new BadSymbol (this.id + ' fetchTrades () supports BTC/JPY only');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.publicGetTrades (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseTrades (data, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'pair': this.marketId (symbol),
|
||||
};
|
||||
if (type === 'market') {
|
||||
const order_type = type + '_' + side;
|
||||
request['order_type'] = order_type;
|
||||
const prefix = (side === 'buy') ? (order_type + '_') : '';
|
||||
request[prefix + 'amount'] = amount;
|
||||
} else {
|
||||
request['order_type'] = side;
|
||||
request['rate'] = price;
|
||||
request['amount'] = amount;
|
||||
}
|
||||
const response = await this.privatePostExchangeOrders (this.extend (request, params));
|
||||
const id = this.safeString (response, 'id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
return await this.privateDeleteExchangeOrdersId (this.extend (request, params));
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
let queryString = '';
|
||||
if (method === 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (this.keysort (query));
|
||||
}
|
||||
} else {
|
||||
if (Object.keys (query).length) {
|
||||
body = this.urlencode (this.keysort (query));
|
||||
queryString = body;
|
||||
}
|
||||
}
|
||||
const auth = nonce + url + queryString;
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'ACCESS-KEY': this.apiKey,
|
||||
'ACCESS-NONCE': nonce,
|
||||
'ACCESS-SIGNATURE': this.hmac (this.encode (auth), this.encode (this.secret)),
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if (api === 'public') {
|
||||
return response;
|
||||
}
|
||||
if ('success' in response) {
|
||||
if (response['success']) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
};
|
||||
+515
@@ -0,0 +1,515 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, InvalidNonce, InsufficientFunds, InvalidOrder, OrderNotFound, DDoSProtection } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinegg extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinegg',
|
||||
'name': 'CoinEgg',
|
||||
'countries': [ 'CN', 'UK' ],
|
||||
'has': {
|
||||
'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',
|
||||
'api': {
|
||||
'web': 'https://trade.coinegg.com/web',
|
||||
'rest': 'https://api.coinegg.com/api/v1',
|
||||
},
|
||||
'www': 'https://www.coinegg.com',
|
||||
'doc': 'https://www.coinegg.com/explain.api.html',
|
||||
'fees': 'https://www.coinegg.com/fee.html',
|
||||
'referral': 'https://www.coinegg.com/user/register?invite=523218',
|
||||
},
|
||||
'api': {
|
||||
'web': {
|
||||
'get': [
|
||||
'symbol/ticker?right_coin={quote}',
|
||||
'{quote}/trends',
|
||||
'{quote}/{base}/order',
|
||||
'{quote}/{base}/trades',
|
||||
'{quote}/{base}/depth.js',
|
||||
],
|
||||
},
|
||||
'public': {
|
||||
'get': [
|
||||
'ticker/region/{quote}',
|
||||
'depth/region/{quote}',
|
||||
'orders/region/{quote}',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'balance',
|
||||
'trade_add/region/{quote}',
|
||||
'trade_cancel/region/{quote}',
|
||||
'trade_view/region/{quote}',
|
||||
'trade_list/region/{quote}',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.1 / 100,
|
||||
'taker': 0.1 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'BTC': 0.008,
|
||||
'BCH': 0.002,
|
||||
'LTC': 0.001,
|
||||
'ETH': 0.01,
|
||||
'ETC': 0.01,
|
||||
'NEO': 0,
|
||||
'QTUM': '1%',
|
||||
'XRP': '1%',
|
||||
'DOGE': '1%',
|
||||
'LSK': '1%',
|
||||
'XAS': '1%',
|
||||
'BTS': '1%',
|
||||
'GAME': '1%',
|
||||
'GOOC': '1%',
|
||||
'NXT': '1%',
|
||||
'IFC': '1%',
|
||||
'DNC': '1%',
|
||||
'BLK': '1%',
|
||||
'VRC': '1%',
|
||||
'XPM': '1%',
|
||||
'VTC': '1%',
|
||||
'TFC': '1%',
|
||||
'PLC': '1%',
|
||||
'EAC': '1%',
|
||||
'PPC': '1%',
|
||||
'FZ': '1%',
|
||||
'ZET': '1%',
|
||||
'RSS': '1%',
|
||||
'PGC': '1%',
|
||||
'SKT': '1%',
|
||||
'JBC': '1%',
|
||||
'RIO': '1%',
|
||||
'LKC': '1%',
|
||||
'ZCC': '1%',
|
||||
'MCC': '1%',
|
||||
'QEC': '1%',
|
||||
'MET': '1%',
|
||||
'YTC': '1%',
|
||||
'HLB': '1%',
|
||||
'MRYC': '1%',
|
||||
'MTC': '1%',
|
||||
'KTC': 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'103': AuthenticationError,
|
||||
'104': AuthenticationError,
|
||||
'105': AuthenticationError,
|
||||
'106': InvalidNonce,
|
||||
'200': InsufficientFunds,
|
||||
'201': InvalidOrder,
|
||||
'202': InvalidOrder,
|
||||
'203': OrderNotFound,
|
||||
'402': DDoSProtection,
|
||||
},
|
||||
'errorMessages': {
|
||||
'100': 'Required parameters can not be empty',
|
||||
'101': 'Illegal parameter',
|
||||
'102': 'coin does not exist',
|
||||
'103': 'Key does not exist',
|
||||
'104': 'Signature does not match',
|
||||
'105': 'Insufficient permissions',
|
||||
'106': 'Request expired(nonce error)',
|
||||
'200': 'Lack of balance',
|
||||
'201': 'Too small for the number of trading',
|
||||
'202': 'Price must be in 0 - 1000000',
|
||||
'203': 'Order does not exist',
|
||||
'204': 'Pending order amount must be above 0.001 BTC',
|
||||
'205': 'Restrict pending order prices',
|
||||
'206': 'Decimal place error',
|
||||
'401': 'System error',
|
||||
'402': 'Requests are too frequent',
|
||||
'403': 'Non-open API',
|
||||
'404': 'IP restriction does not request the resource',
|
||||
'405': 'Currency transactions are temporarily closed',
|
||||
},
|
||||
'options': {
|
||||
'quoteIds': [ 'btc', 'eth', 'usc', 'usdt' ],
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'JBC': 'JubaoCoin',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const quoteIds = this.options['quoteIds'];
|
||||
const result = [];
|
||||
for (let b = 0; b < quoteIds.length; b++) {
|
||||
const quoteId = quoteIds[b];
|
||||
const response = await this.webGetSymbolTickerRightCoinQuote ({
|
||||
'quote': quoteId,
|
||||
});
|
||||
const tickers = this.safeValue (response, 'data', []);
|
||||
for (let i = 0; i < tickers.length; i++) {
|
||||
const ticker = tickers[i];
|
||||
const id = ticker['symbol'];
|
||||
const baseId = id.split ('_')[0];
|
||||
let base = baseId.toUpperCase ();
|
||||
let quote = quoteId.toUpperCase ();
|
||||
base = this.safeCurrencyCode (base);
|
||||
quote = this.safeCurrencyCode (quote);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': true,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision['amount']),
|
||||
'max': Math.pow (10, precision['amount']),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': Math.pow (10, precision['price']),
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': ticker,
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const symbol = market['symbol'];
|
||||
const timestamp = this.milliseconds ();
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
const percentage = this.safeFloat (ticker, 'change');
|
||||
let open = undefined;
|
||||
let change = undefined;
|
||||
let average = undefined;
|
||||
if (percentage !== undefined) {
|
||||
const relativeChange = percentage / 100;
|
||||
open = last / this.sum (1, relativeChange);
|
||||
change = last - open;
|
||||
average = this.sum (last, open) / 2;
|
||||
}
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': open,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': change,
|
||||
'percentage': percentage,
|
||||
'average': average,
|
||||
'baseVolume': this.safeFloat (ticker, 'vol'),
|
||||
'quoteVolume': this.safeFloat (ticker, 'quoteVol'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'coin': market['baseId'],
|
||||
'quote': market['quoteId'],
|
||||
};
|
||||
const response = await this.publicGetTickerRegionQuote (this.extend (request, params));
|
||||
return this.parseTicker (response, market);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'coin': market['baseId'],
|
||||
'quote': market['quoteId'],
|
||||
};
|
||||
const response = await this.publicGetDepthRegionQuote (this.extend (request, params));
|
||||
return this.parseOrderBook (response);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'date');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
const symbol = market['symbol'];
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = this.costToPrecision (symbol, price * amount);
|
||||
}
|
||||
}
|
||||
const type = 'limit';
|
||||
const side = this.safeString (trade, 'type');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': undefined,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'coin': market['baseId'],
|
||||
'quote': market['quoteId'],
|
||||
};
|
||||
const response = await this.publicGetOrdersRegionQuote (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostBalance (params);
|
||||
const result = { 'info': response };
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const balances = this.omit (data, 'uid');
|
||||
const keys = Object.keys (balances);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const [ currencyId, accountType ] = key.split ('_');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
if (!(code in result)) {
|
||||
result[code] = this.account ();
|
||||
}
|
||||
const type = (accountType === 'lock') ? 'used' : 'free';
|
||||
result[code][type] = this.safeFloat (balances, key);
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.parse8601 (this.safeString (order, 'datetime'));
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const amount = this.safeFloat (order, 'amount_original');
|
||||
const remaining = this.safeFloat (order, 'amount_outstanding');
|
||||
let filled = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (remaining !== undefined) {
|
||||
filled = amount - remaining;
|
||||
}
|
||||
}
|
||||
let status = this.safeString (order, 'status');
|
||||
if (status === 'cancelled') {
|
||||
status = 'canceled';
|
||||
} else {
|
||||
status = remaining ? 'open' : 'closed';
|
||||
}
|
||||
const info = this.safeValue (order, 'info', order);
|
||||
const type = 'limit';
|
||||
const side = this.safeString (order, 'type');
|
||||
const id = this.safeString (order, 'id');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': undefined,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': undefined,
|
||||
'fee': undefined,
|
||||
'info': info,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'coin': market['baseId'],
|
||||
'quote': market['quoteId'],
|
||||
'type': side,
|
||||
'amount': amount,
|
||||
'price': price,
|
||||
};
|
||||
const response = await this.privatePostTradeAddRegionQuote (this.extend (request, params));
|
||||
const id = this.safeString (response, 'id');
|
||||
const order = this.parseOrder ({
|
||||
'id': id,
|
||||
'datetime': this.ymdhms (this.milliseconds ()),
|
||||
'amount_original': amount,
|
||||
'amount_outstanding': amount,
|
||||
'price': price,
|
||||
'type': side,
|
||||
'info': response,
|
||||
}, market);
|
||||
return order;
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': id,
|
||||
'coin': market['baseId'],
|
||||
'quote': market['quoteId'],
|
||||
};
|
||||
return await this.privatePostTradeCancelRegionQuote (this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': id,
|
||||
'coin': market['baseId'],
|
||||
'quote': market['quoteId'],
|
||||
};
|
||||
const response = await this.privatePostTradeViewRegionQuote (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'coin': market['baseId'],
|
||||
'quote': market['quoteId'],
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['since'] = since / 1000;
|
||||
}
|
||||
const response = await this.privatePostTradeListRegionQuote (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseOrders (data, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const request = {
|
||||
'type': 'open',
|
||||
};
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let apiType = 'rest';
|
||||
if (api === 'web') {
|
||||
apiType = api;
|
||||
}
|
||||
let url = this.urls['api'][apiType] + '/' + this.implodeParams (path, params);
|
||||
let query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public' || api === 'web') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
query = this.urlencode (this.extend ({
|
||||
'key': this.apiKey,
|
||||
'nonce': this.nonce (),
|
||||
}, query));
|
||||
const secret = this.hash (this.encode (this.secret));
|
||||
const signature = this.hmac (this.encode (query), this.encode (secret));
|
||||
query += '&' + 'signature=' + signature;
|
||||
if (method === 'GET') {
|
||||
url += '?' + query;
|
||||
} else {
|
||||
headers = {
|
||||
'Content-type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
body = query;
|
||||
}
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return;
|
||||
}
|
||||
// private endpoints return the following structure:
|
||||
// {"result":true,"data":{...}} - success
|
||||
// {"result":false,"code":"103"} - failure
|
||||
// {"code":0,"msg":"Suceess","data":{"uid":"2716039","btc_balance":"0.00000000","btc_lock":"0.00000000","xrp_balance":"0.00000000","xrp_lock":"0.00000000"}}
|
||||
const result = this.safeValue (response, 'result');
|
||||
if (result === undefined) {
|
||||
// public endpoint ← this comment left here by the contributor, in fact a missing result does not necessarily mean a public endpoint...
|
||||
// we should just check the code and don't rely on the result at all here...
|
||||
return;
|
||||
}
|
||||
if (result === true) {
|
||||
// success
|
||||
return;
|
||||
}
|
||||
const errorCode = this.safeString (response, 'code');
|
||||
const errorMessages = this.errorMessages;
|
||||
const message = this.safeString (errorMessages, errorCode, 'Unknown Error');
|
||||
const feedback = this.id + ' ' + message;
|
||||
this.throwExactlyMatchedException (this.exceptions, errorCode, feedback);
|
||||
throw new ExchangeError (this.id + ' ' + message);
|
||||
}
|
||||
};
|
||||
+948
@@ -0,0 +1,948 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ArgumentsRequired, InsufficientFunds, OrderNotFound, InvalidOrder, AuthenticationError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinex extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinex',
|
||||
'name': 'CoinEx',
|
||||
'version': 'v1',
|
||||
'countries': [ 'CN' ],
|
||||
'rateLimit': 1000,
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'createOrder': true,
|
||||
'fetchBalance': true,
|
||||
'fetchClosedOrders': 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',
|
||||
'3m': '3min',
|
||||
'5m': '5min',
|
||||
'15m': '15min',
|
||||
'30m': '30min',
|
||||
'1h': '1hour',
|
||||
'2h': '2hour',
|
||||
'4h': '4hour',
|
||||
'6h': '6hour',
|
||||
'12h': '12hour',
|
||||
'1d': '1day',
|
||||
'3d': '3day',
|
||||
'1w': '1week',
|
||||
},
|
||||
'urls': {
|
||||
'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',
|
||||
'fees': 'https://www.coinex.com/fees',
|
||||
'referral': 'https://www.coinex.com/register?refer_code=yw5fz',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'common/currency/rate',
|
||||
'common/asset/config',
|
||||
'market/info',
|
||||
'market/list',
|
||||
'market/ticker',
|
||||
'market/ticker/all',
|
||||
'market/depth',
|
||||
'market/deals',
|
||||
'market/kline',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'balance/coin/deposit',
|
||||
'balance/coin/withdraw',
|
||||
'balance/info',
|
||||
'future/account',
|
||||
'future/config',
|
||||
'future/limitprice',
|
||||
'future/loan/history',
|
||||
'future/market',
|
||||
'margin/account',
|
||||
'margin/config',
|
||||
'margin/loan/history',
|
||||
'margin/market',
|
||||
'order',
|
||||
'order/deals',
|
||||
'order/finished',
|
||||
'order/finished/{id}',
|
||||
'order/pending',
|
||||
'order/status',
|
||||
'order/status/batch',
|
||||
'order/user/deals',
|
||||
],
|
||||
'post': [
|
||||
'balance/coin/withdraw',
|
||||
'future/flat',
|
||||
'future/loan',
|
||||
'future/transfer',
|
||||
'margin/flat',
|
||||
'margin/loan',
|
||||
'margin/transfer',
|
||||
'order/batchlimit',
|
||||
'order/ioc',
|
||||
'order/limit',
|
||||
'order/market',
|
||||
'sub_account/transfer',
|
||||
],
|
||||
'delete': [
|
||||
'balance/coin/withdraw',
|
||||
'order/pending/batch',
|
||||
'order/pending',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.001,
|
||||
'taker': 0.001,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'BCH': 0.0,
|
||||
'BTC': 0.001,
|
||||
'LTC': 0.001,
|
||||
'ETH': 0.001,
|
||||
'ZEC': 0.0001,
|
||||
'DASH': 0.0001,
|
||||
},
|
||||
},
|
||||
},
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': 0.001,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'precision': {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
},
|
||||
'options': {
|
||||
'createMarketBuyOrderRequiresPrice': true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetMarketInfo (params);
|
||||
//
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": {
|
||||
// "WAVESBTC": {
|
||||
// "name": "WAVESBTC",
|
||||
// "min_amount": "1",
|
||||
// "maker_fee_rate": "0.001",
|
||||
// "taker_fee_rate": "0.001",
|
||||
// "pricing_name": "BTC",
|
||||
// "pricing_decimal": 8,
|
||||
// "trading_name": "WAVES",
|
||||
// "trading_decimal": 8
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
const markets = this.safeValue (response, 'data', {});
|
||||
const result = [];
|
||||
const keys = Object.keys (markets);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const market = markets[key];
|
||||
const id = this.safeString (market, 'name');
|
||||
const tradingName = this.safeString (market, 'trading_name');
|
||||
const baseId = tradingName;
|
||||
const quoteId = this.safeString (market, 'pricing_name');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
let symbol = base + '/' + quote;
|
||||
if (tradingName === id) {
|
||||
symbol = id;
|
||||
}
|
||||
const precision = {
|
||||
'amount': this.safeInteger (market, 'trading_decimal'),
|
||||
'price': this.safeInteger (market, 'pricing_decimal'),
|
||||
};
|
||||
const active = undefined;
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'taker': this.safeFloat (market, 'taker_fee_rate'),
|
||||
'maker': this.safeFloat (market, 'maker_fee_rate'),
|
||||
'info': market,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'min_amount'),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.safeInteger (ticker, 'date');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
ticker = this.safeValue (ticker, 'ticker', {});
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat2 (ticker, 'vol', 'volume'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketTicker (this.extend (request, params));
|
||||
return this.parseTicker (response['data'], market);
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetMarketTickerAll (params);
|
||||
const data = this.safeValue (response, 'data');
|
||||
const timestamp = this.safeInteger (data, 'date');
|
||||
const tickers = this.safeValue (data, 'ticker');
|
||||
const marketIds = Object.keys (tickers);
|
||||
const result = {};
|
||||
for (let i = 0; i < marketIds.length; i++) {
|
||||
const marketId = marketIds[i];
|
||||
const market = this.safeMarket (marketId);
|
||||
const symbol = market['symbol'];
|
||||
const ticker = this.parseTicker ({
|
||||
'date': timestamp,
|
||||
'ticker': tickers[marketId],
|
||||
}, market);
|
||||
ticker['symbol'] = symbol;
|
||||
result[symbol] = ticker;
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = 20, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (limit === undefined) {
|
||||
limit = 20; // default
|
||||
}
|
||||
const request = {
|
||||
'market': this.marketId (symbol),
|
||||
'merge': '0.0000000001',
|
||||
'limit': limit.toString (),
|
||||
};
|
||||
const response = await this.publicGetMarketDepth (this.extend (request, params));
|
||||
return this.parseOrderBook (response['data']);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
// this method parses both public and private trades
|
||||
let timestamp = this.safeTimestamp (trade, 'create_time');
|
||||
if (timestamp === undefined) {
|
||||
timestamp = this.safeInteger (trade, 'date_ms');
|
||||
}
|
||||
const tradeId = this.safeString (trade, 'id');
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
const marketId = this.safeString (trade, 'market');
|
||||
const symbol = this.safeSymbol (marketId, market);
|
||||
let cost = this.safeFloat (trade, 'deal_money');
|
||||
if (!cost) {
|
||||
cost = parseFloat (this.costToPrecision (symbol, price * amount));
|
||||
}
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (trade, 'fee');
|
||||
if (feeCost !== undefined) {
|
||||
const feeCurrencyId = this.safeString (trade, 'fee_asset');
|
||||
const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId);
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrencyCode,
|
||||
};
|
||||
}
|
||||
const takerOrMaker = this.safeString (trade, 'role');
|
||||
const side = this.safeString (trade, 'type');
|
||||
return {
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'id': tradeId,
|
||||
'order': orderId,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'takerOrMaker': takerOrMaker,
|
||||
'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 = {
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketDeals (this.extend (request, params));
|
||||
return this.parseTrades (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined) {
|
||||
//
|
||||
// [
|
||||
// 1591484400,
|
||||
// "0.02505349",
|
||||
// "0.02506988",
|
||||
// "0.02507000",
|
||||
// "0.02505304",
|
||||
// "343.19716223",
|
||||
// "8.6021323866383196",
|
||||
// "ETHBTC"
|
||||
// ]
|
||||
//
|
||||
return [
|
||||
this.safeTimestamp (ohlcv, 0),
|
||||
this.safeFloat (ohlcv, 1),
|
||||
this.safeFloat (ohlcv, 3),
|
||||
this.safeFloat (ohlcv, 4),
|
||||
this.safeFloat (ohlcv, 2),
|
||||
this.safeFloat (ohlcv, 5),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
'type': this.timeframes[timeframe],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.publicGetMarketKline (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "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 = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetBalanceInfo (params);
|
||||
//
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": {
|
||||
// "BCH": { # BCH account
|
||||
// "available": "13.60109", # Available BCH
|
||||
// "frozen": "0.00000" # Frozen BCH
|
||||
// },
|
||||
// "BTC": { # BTC account
|
||||
// "available": "32590.16", # Available BTC
|
||||
// "frozen": "7000.00" # Frozen BTC
|
||||
// },
|
||||
// "ETH": { # ETH account
|
||||
// "available": "5.06000", # Available ETH
|
||||
// "frozen": "0.00000" # Frozen ETH
|
||||
// }
|
||||
// },
|
||||
// "message": "Ok"
|
||||
// }
|
||||
//
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'data');
|
||||
const currencyIds = Object.keys (balances);
|
||||
for (let i = 0; i < currencyIds.length; i++) {
|
||||
const currencyId = currencyIds[i];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const balance = this.safeValue (balances, currencyId, {});
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (balance, 'available');
|
||||
account['used'] = this.safeFloat (balance, 'frozen');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'not_deal': 'open',
|
||||
'part_deal': 'open',
|
||||
'done': 'closed',
|
||||
'cancel': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// fetchOrder
|
||||
//
|
||||
// {
|
||||
// "amount": "0.1",
|
||||
// "asset_fee": "0.22736197736197736197",
|
||||
// "avg_price": "196.85000000000000000000",
|
||||
// "create_time": 1537270135,
|
||||
// "deal_amount": "0.1",
|
||||
// "deal_fee": "0",
|
||||
// "deal_money": "19.685",
|
||||
// "fee_asset": "CET",
|
||||
// "fee_discount": "0.5",
|
||||
// "id": 1788259447,
|
||||
// "left": "0",
|
||||
// "maker_fee_rate": "0",
|
||||
// "market": "ETHUSDT",
|
||||
// "order_type": "limit",
|
||||
// "price": "170.00000000",
|
||||
// "status": "done",
|
||||
// "taker_fee_rate": "0.0005",
|
||||
// "type": "sell",
|
||||
// }
|
||||
//
|
||||
const timestamp = this.safeTimestamp (order, 'create_time');
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const cost = this.safeFloat (order, 'deal_money');
|
||||
const amount = this.safeFloat (order, 'amount');
|
||||
const filled = this.safeFloat (order, 'deal_amount');
|
||||
const average = this.safeFloat (order, 'avg_price');
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (order, 'market');
|
||||
market = this.safeMarket (marketId, market);
|
||||
const feeCurrencyId = this.safeString (order, 'fee_asset');
|
||||
let feeCurrency = this.safeCurrencyCode (feeCurrencyId);
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
if (feeCurrency === undefined) {
|
||||
feeCurrency = market['quote'];
|
||||
}
|
||||
}
|
||||
const remaining = this.safeFloat (order, 'left');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const type = this.safeString (order, 'order_type');
|
||||
const side = this.safeString (order, 'type');
|
||||
return {
|
||||
'id': this.safeString (order, 'id'),
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'average': average,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': undefined,
|
||||
'fee': {
|
||||
'currency': feeCurrency,
|
||||
'cost': this.safeFloat (order, 'deal_fee'),
|
||||
},
|
||||
'info': order,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const method = 'privatePostOrder' + this.capitalize (type);
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
'type': side,
|
||||
};
|
||||
amount = parseFloat (amount);
|
||||
// for market buy it requires the amount of quote currency to spend
|
||||
if ((type === 'market') && (side === 'buy')) {
|
||||
if (this.options['createMarketBuyOrderRequiresPrice']) {
|
||||
if (price === undefined) {
|
||||
throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)");
|
||||
} else {
|
||||
price = parseFloat (price);
|
||||
request['amount'] = this.costToPrecision (symbol, amount * price);
|
||||
}
|
||||
} else {
|
||||
request['amount'] = this.costToPrecision (symbol, amount);
|
||||
}
|
||||
} else {
|
||||
request['amount'] = this.amountToPrecision (symbol, amount);
|
||||
}
|
||||
if ((type === 'limit') || (type === 'ioc')) {
|
||||
request['price'] = this.priceToPrecision (symbol, price);
|
||||
}
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': id,
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.privateDeleteOrderPending (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
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 = {
|
||||
'id': id,
|
||||
'market': market['id'],
|
||||
};
|
||||
const response = await this.privateGetOrder (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": {
|
||||
// "amount": "0.1",
|
||||
// "asset_fee": "0.22736197736197736197",
|
||||
// "avg_price": "196.85000000000000000000",
|
||||
// "create_time": 1537270135,
|
||||
// "deal_amount": "0.1",
|
||||
// "deal_fee": "0",
|
||||
// "deal_money": "19.685",
|
||||
// "fee_asset": "CET",
|
||||
// "fee_discount": "0.5",
|
||||
// "id": 1788259447,
|
||||
// "left": "0",
|
||||
// "maker_fee_rate": "0",
|
||||
// "market": "ETHUSDT",
|
||||
// "order_type": "limit",
|
||||
// "price": "170.00000000",
|
||||
// "status": "done",
|
||||
// "taker_fee_rate": "0.0005",
|
||||
// "type": "sell",
|
||||
// },
|
||||
// "message": "Ok"
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (limit === undefined) {
|
||||
limit = 100;
|
||||
}
|
||||
const request = {
|
||||
'page': 1,
|
||||
'limit': limit,
|
||||
};
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['market'] = market['id'];
|
||||
}
|
||||
const method = 'privateGetOrder' + this.capitalize (status);
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
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 = {}) {
|
||||
return await this.fetchOrdersByStatus ('pending', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchOrdersByStatus ('finished', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (limit === undefined) {
|
||||
limit = 100;
|
||||
}
|
||||
const request = {
|
||||
'page': 1,
|
||||
'limit': limit,
|
||||
};
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['market'] = market['id'];
|
||||
}
|
||||
const response = await this.privateGetOrderUserDeals (this.extend (request, params));
|
||||
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 = {}) {
|
||||
this.checkAddress (address);
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
if (tag) {
|
||||
address = address + ':' + tag;
|
||||
}
|
||||
const request = {
|
||||
'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': 'onchain', // onchain, local
|
||||
};
|
||||
const response = await this.privatePostBalanceCoinWithdraw (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "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": ""
|
||||
// },
|
||||
// "message": "Ok"
|
||||
// }
|
||||
//
|
||||
const transaction = this.safeValue (response, 'data', {});
|
||||
return this.parseTransaction (transaction, currency);
|
||||
}
|
||||
|
||||
parseTransactionStatus (status) {
|
||||
const statuses = {
|
||||
'audit': 'pending',
|
||||
'pass': 'pending',
|
||||
'processing': 'pending',
|
||||
'confirming': 'pending',
|
||||
'not_pass': 'failed',
|
||||
'cancel': 'canceled',
|
||||
'finish': 'ok',
|
||||
'fail': 'failed',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseTransaction (transaction, currency = undefined) {
|
||||
//
|
||||
// fetchDeposits
|
||||
//
|
||||
// {
|
||||
// "actual_amount": "120.00000000",
|
||||
// "actual_amount_display": "120",
|
||||
// "add_explorer": "XXX",
|
||||
// "amount": "120.00000000",
|
||||
// "amount_display": "120",
|
||||
// "coin_address": "XXXXXXXX",
|
||||
// "coin_address_display": "XXXXXXXX",
|
||||
// "coin_deposit_id": 1866,
|
||||
// "coin_type": "USDT",
|
||||
// "confirmations": 0,
|
||||
// "create_time": 1539595701,
|
||||
// "explorer": "",
|
||||
// "remark": "",
|
||||
// "status": "finish",
|
||||
// "status_display": "finish",
|
||||
// "transfer_method": "local",
|
||||
// "tx_id": "",
|
||||
// "tx_id_display": "XXXXXXXXXX"
|
||||
// }
|
||||
//
|
||||
// fetchWithdrawals
|
||||
//
|
||||
// {
|
||||
// "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"
|
||||
// }
|
||||
//
|
||||
const id = this.safeString2 (transaction, 'coin_withdraw_id', 'coin_deposit_id');
|
||||
const address = this.safeString (transaction, 'coin_address');
|
||||
let tag = this.safeString (transaction, 'remark'); // set but unused
|
||||
if (tag !== undefined) {
|
||||
if (tag.length < 1) {
|
||||
tag = undefined;
|
||||
}
|
||||
}
|
||||
let txid = this.safeValue (transaction, 'tx_id');
|
||||
if (txid !== undefined) {
|
||||
if (txid.length < 1) {
|
||||
txid = undefined;
|
||||
}
|
||||
}
|
||||
const currencyId = this.safeString (transaction, 'coin_type');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const timestamp = this.safeTimestamp (transaction, 'create_time');
|
||||
const type = ('coin_withdraw_id' in transaction) ? 'withdraw' : 'deposit';
|
||||
const status = this.parseTransactionStatus (this.safeString (transaction, 'status'));
|
||||
const amount = this.safeFloat (transaction, 'amount');
|
||||
let feeCost = this.safeFloat (transaction, 'tx_fee');
|
||||
if (type === 'deposit') {
|
||||
feeCost = 0;
|
||||
}
|
||||
const fee = {
|
||||
'cost': feeCost,
|
||||
'currency': code,
|
||||
};
|
||||
return {
|
||||
'info': transaction,
|
||||
'id': id,
|
||||
'txid': txid,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'address': address,
|
||||
'tag': tag,
|
||||
'type': type,
|
||||
'amount': amount,
|
||||
'currency': code,
|
||||
'status': status,
|
||||
'updated': undefined,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (code === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a currency code argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'coin_type': currency['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['Limit'] = limit;
|
||||
}
|
||||
const response = await this.privateGetBalanceCoinWithdraw (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "code": 0,
|
||||
// "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"
|
||||
// }
|
||||
//
|
||||
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 = {}) {
|
||||
if (code === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchDeposits requires a currency code argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'coin_type': currency['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['Limit'] = limit;
|
||||
}
|
||||
const response = await this.privateGetBalanceCoinDeposit (this.extend (request, params));
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": [
|
||||
// {
|
||||
// "actual_amount": "4.65397682",
|
||||
// "actual_amount_display": "4.65397682",
|
||||
// "add_explorer": "https://etherscan.io/address/0x361XXXXXX",
|
||||
// "amount": "4.65397682",
|
||||
// "amount_display": "4.65397682",
|
||||
// "coin_address": "0x36dabcdXXXXXX",
|
||||
// "coin_address_display": "0x361X*****XXXXX",
|
||||
// "coin_deposit_id": 966191,
|
||||
// "coin_type": "ETH",
|
||||
// "confirmations": 30,
|
||||
// "create_time": 1531661445,
|
||||
// "explorer": "https://etherscan.io/tx/0x361XXXXXX",
|
||||
// "remark": "",
|
||||
// "status": "finish",
|
||||
// "status_display": "finish",
|
||||
// "transfer_method": "onchain",
|
||||
// "tx_id": "0x361XXXXXX",
|
||||
// "tx_id_display": "0x361XXXXXX"
|
||||
// }
|
||||
// ],
|
||||
// "message": "Ok"
|
||||
// }
|
||||
//
|
||||
let data = this.safeValue (response, 'data');
|
||||
if (!Array.isArray (data)) {
|
||||
data = this.safeValue (data, 'data', []);
|
||||
}
|
||||
return this.parseTransactions (data, currency, since, limit);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
path = this.implodeParams (path, params);
|
||||
let url = this.urls['api'] + '/' + this.version + '/' + path;
|
||||
let query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
query = this.extend ({
|
||||
'access_id': this.apiKey,
|
||||
'tonce': nonce.toString (),
|
||||
}, query);
|
||||
query = this.keysort (query);
|
||||
const urlencoded = this.urlencode (query);
|
||||
const signature = this.hash (this.encode (urlencoded + '&secret_key=' + this.secret));
|
||||
headers = {
|
||||
'Authorization': signature.toUpperCase (),
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if ((method === 'GET') || (method === 'DELETE')) {
|
||||
url += '?' + urlencoded;
|
||||
} else {
|
||||
body = this.json (query);
|
||||
}
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
const code = this.safeString (response, 'code');
|
||||
const data = this.safeValue (response, 'data');
|
||||
const message = this.safeString (response, 'message');
|
||||
if ((code !== '0') || (data === undefined) || ((message !== 'Ok') && !data)) {
|
||||
const responseCodes = {
|
||||
'24': AuthenticationError,
|
||||
'25': AuthenticationError,
|
||||
'107': InsufficientFunds,
|
||||
'600': OrderNotFound,
|
||||
'601': InvalidOrder,
|
||||
'602': InvalidOrder,
|
||||
'606': InvalidOrder,
|
||||
};
|
||||
const ErrorClass = this.safeValue (responseCodes, code, ExchangeError);
|
||||
throw new ErrorClass (response['message']);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, RateLimitExceeded, ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinfalcon extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinfalcon',
|
||||
'name': 'CoinFalcon',
|
||||
'countries': [ 'GB' ],
|
||||
'rateLimit': 1000,
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'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',
|
||||
'api': 'https://coinfalcon.com',
|
||||
'www': 'https://coinfalcon.com',
|
||||
'doc': 'https://docs.coinfalcon.com',
|
||||
'fees': 'https://coinfalcon.com/fees',
|
||||
'referral': 'https://coinfalcon.com/?ref=CFJSVGTUPASB',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'markets',
|
||||
'markets/{market}/orders',
|
||||
'markets/{market}/trades',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'user/accounts',
|
||||
'user/orders',
|
||||
'user/orders/{id}',
|
||||
'user/trades',
|
||||
],
|
||||
'post': [
|
||||
'user/orders',
|
||||
],
|
||||
'delete': [
|
||||
'user/orders/{id}',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'maker': 0.0,
|
||||
'taker': 0.002, // tiered fee starts at 0.2%
|
||||
},
|
||||
},
|
||||
'precision': {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetMarkets (params);
|
||||
const markets = this.safeValue (response, 'data');
|
||||
const result = [];
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const [ baseId, quoteId ] = market['name'].split ('-');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': this.safeInteger (market, 'size_precision'),
|
||||
'price': this.safeInteger (market, 'price_precision'),
|
||||
};
|
||||
result.push ({
|
||||
'id': market['name'],
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': true,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision['amount']),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const marketId = this.safeString (ticker, 'name');
|
||||
const symbol = this.safeSymbol (marketId, market, '-');
|
||||
const timestamp = this.milliseconds ();
|
||||
const last = parseFloat (ticker['last_price']);
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': undefined,
|
||||
'bidVolume': undefined,
|
||||
'ask': undefined,
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': this.safeFloat (ticker, 'change_in_24h'),
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': undefined,
|
||||
'quoteVolume': this.safeFloat (ticker, 'volume'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const tickers = await this.fetchTickers (params);
|
||||
return tickers[symbol];
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetMarkets (params);
|
||||
const tickers = this.safeValue (response, 'data');
|
||||
const result = {};
|
||||
for (let i = 0; i < tickers.length; i++) {
|
||||
const ticker = this.parseTicker (tickers[i]);
|
||||
const symbol = ticker['symbol'];
|
||||
result[symbol] = ticker;
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'market': this.marketId (symbol),
|
||||
'level': '3',
|
||||
};
|
||||
const response = await this.publicGetMarketsMarketOrders (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
return this.parseOrderBook (data, undefined, 'bids', 'asks', 'price', 'size');
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (trade, 'created_at'));
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'size');
|
||||
const symbol = market['symbol'];
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = parseFloat (this.costToPrecision (symbol, price * amount));
|
||||
}
|
||||
}
|
||||
const tradeId = this.safeString (trade, 'id');
|
||||
const side = this.safeString (trade, 'side');
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (trade, 'fee');
|
||||
if (feeCost !== undefined) {
|
||||
const feeCurrencyCode = this.safeString (trade, 'fee_currency_code');
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': this.safeCurrencyCode (feeCurrencyCode),
|
||||
};
|
||||
}
|
||||
return {
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'id': tradeId,
|
||||
'order': orderId,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
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 = {
|
||||
'market': market['id'],
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['start_time'] = this.iso8601 (since);
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.privateGetUserTrades (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseTrades (data, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['since'] = this.iso8601 (since);
|
||||
}
|
||||
const response = await this.publicGetMarketsMarketTrades (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseTrades (data, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetUserAccounts (params);
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'data');
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[i];
|
||||
const currencyId = this.safeString (balance, 'currency_code');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = {
|
||||
'free': this.safeFloat (balance, 'available_balance'),
|
||||
'used': this.safeFloat (balance, 'hold_balance'),
|
||||
'total': this.safeFloat (balance, 'balance'),
|
||||
};
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'fulfilled': 'closed',
|
||||
'canceled': 'canceled',
|
||||
'pending': 'open',
|
||||
'open': 'open',
|
||||
'partially_filled': 'open',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// {
|
||||
// "id":"8bdd79f4-8414-40a2-90c3-e9f4d6d1eef4"
|
||||
// "market":"IOT-BTC"
|
||||
// "price":"0.0000003"
|
||||
// "size":"4.0"
|
||||
// "size_filled":"3.0"
|
||||
// "fee":"0.0075"
|
||||
// "fee_currency_code":"iot"
|
||||
// "funds":"0.0"
|
||||
// "status":"canceled"
|
||||
// "order_type":"buy"
|
||||
// "post_only":false
|
||||
// "operation_type":"market_order"
|
||||
// "created_at":"2018-01-12T21:14:06.747828Z"
|
||||
// }
|
||||
//
|
||||
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');
|
||||
const filled = this.safeFloat (order, 'size_filled');
|
||||
let remaining = undefined;
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (filled !== undefined) {
|
||||
remaining = Math.max (0, amount - filled);
|
||||
}
|
||||
if (price !== undefined) {
|
||||
cost = filled * price;
|
||||
}
|
||||
}
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
let type = this.safeString (order, 'operation_type');
|
||||
if (type !== undefined) {
|
||||
type = type.split ('_');
|
||||
type = type[0];
|
||||
}
|
||||
const side = this.safeString (order, 'order_type');
|
||||
return {
|
||||
'id': this.safeString (order, 'id'),
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': undefined,
|
||||
'fee': undefined,
|
||||
'info': order,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
// price/size must be string
|
||||
const request = {
|
||||
'market': market['id'],
|
||||
'size': this.amountToPrecision (symbol, amount),
|
||||
'order_type': side,
|
||||
};
|
||||
if (type === 'limit') {
|
||||
price = this.priceToPrecision (symbol, price);
|
||||
request['price'] = price.toString ();
|
||||
}
|
||||
request['operation_type'] = type + '_order';
|
||||
const response = await this.privatePostUserOrders (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privateDeleteUserOrdersId (this.extend (request, params));
|
||||
const market = this.market (symbol);
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privateGetUserOrdersId (this.extend (request, params));
|
||||
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) {
|
||||
market = this.market (symbol);
|
||||
request['market'] = market['id'];
|
||||
}
|
||||
if (since !== undefined) {
|
||||
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));
|
||||
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 () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let request = '/api/' + this.version + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
request += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
if (method === 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
request += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
body = this.json (query);
|
||||
}
|
||||
const seconds = this.seconds ().toString ();
|
||||
let payload = [ seconds, method, request ].join ('|');
|
||||
if (body) {
|
||||
payload += '|' + body;
|
||||
}
|
||||
const signature = this.hmac (this.encode (payload), this.encode (this.secret));
|
||||
headers = {
|
||||
'CF-API-KEY': this.apiKey,
|
||||
'CF-API-TIMESTAMP': seconds,
|
||||
'CF-API-SIGNATURE': signature,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
const url = this.urls['api'] + request;
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (code < 400) {
|
||||
return;
|
||||
}
|
||||
const ErrorClass = this.safeValue ({
|
||||
'401': AuthenticationError,
|
||||
'429': RateLimitExceeded,
|
||||
}, code, ExchangeError);
|
||||
throw new ErrorClass (body);
|
||||
}
|
||||
};
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { InsufficientFunds, ExchangeError, InvalidNonce, InvalidOrder, ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinfloor extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinfloor',
|
||||
'name': 'coinfloor',
|
||||
'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/51840849/87153925-ef265e80-c2c0-11ea-91b5-020c804b90e0.jpg',
|
||||
'api': 'https://webapi.coinfloor.co.uk/bist',
|
||||
'www': 'https://www.coinfloor.co.uk',
|
||||
'doc': [
|
||||
'https://github.com/coinfloor/api',
|
||||
'https://www.coinfloor.co.uk/api',
|
||||
],
|
||||
},
|
||||
'requiredCredentials': {
|
||||
'apiKey': true,
|
||||
'secret': false,
|
||||
'password': true,
|
||||
'uid': true,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'{id}/ticker/',
|
||||
'{id}/order_book/',
|
||||
'{id}/transactions/',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'{id}/balance/',
|
||||
'{id}/user_transactions/',
|
||||
'{id}/open_orders/',
|
||||
'{symbol}/cancel_order/',
|
||||
'{id}/buy/',
|
||||
'{id}/sell/',
|
||||
'{id}/buy_market/',
|
||||
'{id}/sell_market/',
|
||||
'{id}/estimate_sell_market/',
|
||||
'{id}/estimate_buy_market/',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/GBP': { 'id': 'XBT/GBP', 'symbol': 'BTC/GBP', 'base': 'BTC', 'quote': 'GBP', 'baseId': 'XBT', 'quoteId': 'GBP', 'precision': { 'price': 0, 'amount': 4 }},
|
||||
'BTC/EUR': { 'id': 'XBT/EUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'XBT', 'quoteId': 'EUR', 'precision': { 'price': 0, 'amount': 4 }},
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {
|
||||
'You have insufficient funds.': InsufficientFunds,
|
||||
'Tonce is out of sequence.': InvalidNonce,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
let query = params;
|
||||
const symbol = this.safeString (params, 'symbol');
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (params['symbol']);
|
||||
query = this.omit (params, 'symbol');
|
||||
}
|
||||
const marketId = this.safeString (params, 'id');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
if (market === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchBalance requires a symbol param');
|
||||
}
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
};
|
||||
const response = await this.privatePostIdBalance (this.extend (request, query));
|
||||
const result = {
|
||||
'info': response,
|
||||
};
|
||||
// base/quote used for keys e.g. "xbt_reserved"
|
||||
const base = market['base'];
|
||||
const quote = market['quote'];
|
||||
const baseIdLower = this.safeStringLower (market, 'baseId');
|
||||
const quoteIdLower = this.safeStringLower (market, 'quoteId');
|
||||
result[base] = {
|
||||
'free': this.safeFloat (response, baseIdLower + '_available'),
|
||||
'used': this.safeFloat (response, baseIdLower + '_reserved'),
|
||||
'total': this.safeFloat (response, baseIdLower + '_balance'),
|
||||
};
|
||||
result[quote] = {
|
||||
'free': this.safeFloat (response, quoteIdLower + '_available'),
|
||||
'used': this.safeFloat (response, quoteIdLower + '_reserved'),
|
||||
'total': this.safeFloat (response, quoteIdLower + '_balance'),
|
||||
};
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetIdOrderBook (this.extend (request, params));
|
||||
return this.parseOrderBook (response);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
// rewrite to get the timestamp from HTTP headers
|
||||
const timestamp = this.milliseconds ();
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const vwap = this.safeFloat (ticker, 'vwap');
|
||||
const baseVolume = this.safeFloat (ticker, 'volume');
|
||||
let quoteVolume = undefined;
|
||||
if (vwap !== undefined) {
|
||||
quoteVolume = baseVolume * vwap;
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': vwap,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': baseVolume,
|
||||
'quoteVolume': quoteVolume,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
};
|
||||
const response = await this.publicGetIdTicker (this.extend (request, params));
|
||||
return this.parseTicker (response, market);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'date');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'order': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
};
|
||||
const response = await this.publicGetIdTransactions (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
// code is actually a market symbol in this situation, not a currency code
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
if (code !== undefined) {
|
||||
market = this.market (code);
|
||||
if (market === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchTransactions requires a code argument (a market symbol)');
|
||||
}
|
||||
}
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
'limit': limit,
|
||||
};
|
||||
const response = await this.privatePostIdUserTransactions (this.extend (request, params));
|
||||
return this.parseLedger (response, undefined, since, undefined);
|
||||
}
|
||||
|
||||
parseLedgerEntryStatus (status) {
|
||||
const types = {
|
||||
'completed': 'ok',
|
||||
};
|
||||
return this.safeString (types, status, status);
|
||||
}
|
||||
|
||||
parseLedgerEntryType (type) {
|
||||
const types = {
|
||||
'0': 'transaction', // deposit
|
||||
'1': 'transaction', // withdrawal
|
||||
'2': 'trade',
|
||||
};
|
||||
return this.safeString (types, type, type);
|
||||
}
|
||||
|
||||
parseLedgerEntry (item, currency = undefined) {
|
||||
//
|
||||
// trade
|
||||
//
|
||||
// {
|
||||
// "datetime": "2017-07-25 06:41:24",
|
||||
// "id": 1500964884381265,
|
||||
// "type": 2,
|
||||
// "xbt": "0.1000",
|
||||
// "xbt_eur": "2322.00",
|
||||
// "eur": "-232.20",
|
||||
// "fee": "0.00",
|
||||
// "order_id": 84696745
|
||||
// }
|
||||
//
|
||||
// transaction (withdrawal)
|
||||
//
|
||||
// {
|
||||
// "datetime": "2017-07-25 13:19:46",
|
||||
// "id": 97669,
|
||||
// "type": 1,
|
||||
// "xbt": "-3.0000",
|
||||
// "xbt_eur": null,
|
||||
// "eur": "0",
|
||||
// "fee": "0.0000",
|
||||
// "order_id": null
|
||||
// }
|
||||
//
|
||||
// transaction (deposit)
|
||||
//
|
||||
// {
|
||||
// "datetime": "2017-07-27 16:44:55",
|
||||
// "id": 98277,
|
||||
// "type": 0,
|
||||
// "xbt": "0",
|
||||
// "xbt_eur": null,
|
||||
// "eur": "4970.04",
|
||||
// "fee": "0.00",
|
||||
// "order_id": null
|
||||
// }
|
||||
//
|
||||
const keys = Object.keys (item);
|
||||
let baseId = undefined;
|
||||
let quoteId = undefined;
|
||||
let baseAmount = undefined;
|
||||
let quoteAmount = undefined;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (key.indexOf ('_') > 0) {
|
||||
const parts = key.split ('_');
|
||||
const numParts = parts.length;
|
||||
if (numParts === 2) {
|
||||
const tmpBaseAmount = this.safeFloat (item, parts[0]);
|
||||
const tmpQuoteAmount = this.safeFloat (item, parts[1]);
|
||||
if (tmpBaseAmount !== undefined && tmpQuoteAmount !== undefined) {
|
||||
baseId = parts[0];
|
||||
quoteId = parts[1];
|
||||
baseAmount = tmpBaseAmount;
|
||||
quoteAmount = tmpQuoteAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const type = this.parseLedgerEntryType (this.safeString (item, 'type'));
|
||||
const referenceId = this.safeString (item, 'id');
|
||||
const timestamp = this.parse8601 (this.safeString (item, 'datetime'));
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (item, 'fee');
|
||||
const result = {
|
||||
'id': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'amount': undefined,
|
||||
'direction': undefined,
|
||||
'currency': undefined,
|
||||
'type': type,
|
||||
'referenceId': referenceId,
|
||||
'referenceAccount': undefined,
|
||||
'before': undefined,
|
||||
'after': undefined,
|
||||
'status': 'ok',
|
||||
'fee': fee,
|
||||
'info': item,
|
||||
};
|
||||
if (type === 'trade') {
|
||||
//
|
||||
// it's a trade so let's make multiple entries, we have several options:
|
||||
//
|
||||
// if fee is always in quote currency (the exchange uses this)
|
||||
// https://github.com/coinfloor/API/blob/master/IMPL-GUIDE.md#how-fees-affect-trade-quantities
|
||||
//
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': quote,
|
||||
};
|
||||
}
|
||||
return [
|
||||
this.extend (result, { 'currency': base, 'amount': Math.abs (baseAmount), 'direction': (baseAmount > 0) ? 'in' : 'out' }),
|
||||
this.extend (result, { 'currency': quote, 'amount': Math.abs (quoteAmount), 'direction': (quoteAmount > 0) ? 'in' : 'out', 'fee': fee }),
|
||||
];
|
||||
//
|
||||
// if fee is base or quote depending on buy/sell side
|
||||
//
|
||||
// const baseFee = (baseAmount > 0) ? { 'currency': base, 'cost': feeCost } : undefined;
|
||||
// const quoteFee = (quoteAmount > 0) ? { 'currency': quote, 'cost': feeCost } : undefined;
|
||||
// return [
|
||||
// this.extend (result, { 'currency': base, 'amount': baseAmount, 'direction': (baseAmount > 0) ? 'in' : 'out', 'fee': baseFee }),
|
||||
// this.extend (result, { 'currency': quote, 'amount': quoteAmount, 'direction': (quoteAmount > 0) ? 'in' : 'out', 'fee': quoteFee }),
|
||||
// ];
|
||||
//
|
||||
// fee as the 3rd item
|
||||
//
|
||||
// return [
|
||||
// this.extend (result, { 'currency': base, 'amount': baseAmount, 'direction': (baseAmount > 0) ? 'in' : 'out' }),
|
||||
// this.extend (result, { 'currency': quote, 'amount': quoteAmount, 'direction': (quoteAmount > 0) ? 'in' : 'out' }),
|
||||
// this.extend (result, { 'currency': feeCurrency, 'amount': feeCost, 'direction': 'out', 'type': 'fee' }),
|
||||
// ];
|
||||
//
|
||||
} else {
|
||||
//
|
||||
// it's a regular transaction (deposit or withdrawal)
|
||||
//
|
||||
const amount = (baseAmount === 0) ? quoteAmount : baseAmount;
|
||||
const code = (baseAmount === 0) ? quote : base;
|
||||
const direction = (amount > 0) ? 'in' : 'out';
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': code,
|
||||
};
|
||||
}
|
||||
return this.extend (result, {
|
||||
'currency': code,
|
||||
'amount': Math.abs (amount),
|
||||
'direction': direction,
|
||||
'fee': fee,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': this.marketId (symbol),
|
||||
};
|
||||
let method = 'privatePostId' + this.capitalize (side);
|
||||
if (type === 'market') {
|
||||
request['quantity'] = amount;
|
||||
method += 'Market';
|
||||
} else {
|
||||
request['price'] = price;
|
||||
request['amount'] = amount;
|
||||
}
|
||||
//
|
||||
// {
|
||||
// "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 = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privatePostSymbolCancelOrder (request);
|
||||
if (response === 'false') {
|
||||
// unfortunately the exchange does not give much info in the response
|
||||
throw new InvalidOrder (this.id + ' cancel was rejected');
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (order, 'datetime'));
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const amount = this.safeFloat (order, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
let side = undefined;
|
||||
const status = this.safeString (order, 'status');
|
||||
if (order['type'] === 0) {
|
||||
side = 'buy';
|
||||
} else if (order['type'] === 1) {
|
||||
side = 'sell';
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const id = this.safeString (order, 'id');
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'amount': undefined,
|
||||
'filled': undefined,
|
||||
'remaining': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
'average': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol param');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
};
|
||||
const response = await this.privatePostIdOpenOrders (this.extend (request, params));
|
||||
// {
|
||||
// "amount": "1.0000",
|
||||
// "datetime": "2019-07-12 13:28:16",
|
||||
// "id": 233123443,
|
||||
// "price": "1000.00",
|
||||
// "type": 0
|
||||
// }
|
||||
return this.parseOrders (response, market, since, limit, { 'status': 'open' });
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (code < 400) {
|
||||
return;
|
||||
}
|
||||
if (response === undefined) {
|
||||
return;
|
||||
}
|
||||
const message = this.safeString (response, 'error_msg');
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
// curl -k -u '[User ID]/[API key]:[Passphrase]' https://webapi.coinfloor.co.uk:8090/bist/XBT/GBP/balance/
|
||||
let url = this.urls['api'] + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
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 (auth));
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Basic ' + signature,
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coingi extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coingi',
|
||||
'name': 'Coingi',
|
||||
'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',
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/28619707-5c9232a8-7212-11e7-86d6-98fe5d15cc6e.jpg',
|
||||
'api': {
|
||||
'www': 'https://coingi.com',
|
||||
'current': 'https://api.coingi.com',
|
||||
'user': 'https://api.coingi.com',
|
||||
},
|
||||
'www': 'https://coingi.com',
|
||||
'doc': 'https://coingi.docs.apiary.io',
|
||||
},
|
||||
'api': {
|
||||
'www': {
|
||||
'get': [
|
||||
'',
|
||||
],
|
||||
},
|
||||
'current': {
|
||||
'get': [
|
||||
'order-book/{pair}/{askCount}/{bidCount}/{depth}',
|
||||
'transactions/{pair}/{maxCount}',
|
||||
'24hour-rolling-aggregation',
|
||||
],
|
||||
},
|
||||
'user': {
|
||||
'post': [
|
||||
'balance',
|
||||
'add-order',
|
||||
'cancel-order',
|
||||
'orders',
|
||||
'transactions',
|
||||
'create-crypto-withdrawal',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'taker': 0.2 / 100,
|
||||
'maker': 0.2 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': false,
|
||||
'withdraw': {
|
||||
'BTC': 0.001,
|
||||
'LTC': 0.01,
|
||||
'DOGE': 2,
|
||||
'PPC': 0.02,
|
||||
'VTC': 0.2,
|
||||
'NMC': 2,
|
||||
'DASH': 0.002,
|
||||
'USD': 10,
|
||||
'EUR': 10,
|
||||
},
|
||||
'deposit': {
|
||||
'BTC': 0,
|
||||
'LTC': 0,
|
||||
'DOGE': 0,
|
||||
'PPC': 0,
|
||||
'VTC': 0,
|
||||
'NMC': 0,
|
||||
'DASH': 0,
|
||||
'USD': 5,
|
||||
'EUR': 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.wwwGet (params);
|
||||
const parts = response.split ('do=currencyPairSelector-selectCurrencyPair" class="active">');
|
||||
const currencyParts = parts[1].split ('<div class="currency-pair-label">');
|
||||
const result = [];
|
||||
for (let i = 1; i < currencyParts.length; i++) {
|
||||
const currencyPart = currencyParts[i];
|
||||
const idParts = currencyPart.split ('</div>');
|
||||
let id = idParts[0];
|
||||
id = id.replace ('/', '-');
|
||||
id = id.toLowerCase ();
|
||||
const [ baseId, quoteId ] = id.split ('-');
|
||||
let base = baseId.toUpperCase ();
|
||||
let quote = quoteId.toUpperCase ();
|
||||
base = this.safeCurrencyCode (base);
|
||||
quote = this.safeCurrencyCode (quote);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': 8,
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'info': id,
|
||||
'active': true,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision['amount']),
|
||||
'max': Math.pow (10, precision['amount']),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': 0,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const lowercaseCurrencies = [];
|
||||
const currencies = Object.keys (this.currencies);
|
||||
for (let i = 0; i < currencies.length; i++) {
|
||||
const currency = currencies[i];
|
||||
lowercaseCurrencies.push (currency.toLowerCase ());
|
||||
}
|
||||
const request = {
|
||||
'currencies': lowercaseCurrencies.join (','),
|
||||
};
|
||||
const response = await this.userPostBalance (this.extend (request, params));
|
||||
const result = { 'info': response };
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const balance = response[i];
|
||||
const currencyId = this.safeString (balance['currency'], 'name');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (balance, 'available');
|
||||
const blocked = this.safeFloat (balance, 'blocked');
|
||||
const inOrders = this.safeFloat (balance, 'inOrders');
|
||||
const withdrawing = this.safeFloat (balance, 'withdrawing');
|
||||
account['used'] = this.sum (blocked, inOrders, withdrawing);
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = 512, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
'depth': 32, // maximum number of depth range steps 1-32
|
||||
'askCount': limit, // maximum returned number of asks 1-512
|
||||
'bidCount': limit, // maximum returned number of bids 1-512
|
||||
};
|
||||
const orderbook = await this.currentGetOrderBookPairAskCountBidCountDepth (this.extend (request, params));
|
||||
return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'baseAmount');
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.milliseconds ();
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'highestBid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'lowestAsk'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': undefined,
|
||||
'last': undefined,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'baseVolume'),
|
||||
'quoteVolume': this.safeFloat (ticker, 'counterVolume'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.currentGet24hourRollingAggregation (params);
|
||||
const result = {};
|
||||
for (let t = 0; t < response.length; t++) {
|
||||
const ticker = response[t];
|
||||
const base = ticker['currencyPair']['base'].toUpperCase ();
|
||||
const quote = ticker['currencyPair']['counter'].toUpperCase ();
|
||||
const symbol = base + '/' + quote;
|
||||
let market = undefined;
|
||||
if (symbol in this.markets) {
|
||||
market = this.markets[symbol];
|
||||
}
|
||||
result[symbol] = this.parseTicker (ticker, market);
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const tickers = await this.fetchTickers (undefined, params);
|
||||
if (symbol in tickers) {
|
||||
return tickers[symbol];
|
||||
}
|
||||
throw new ExchangeError (this.id + ' return did not contain ' + symbol);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
const timestamp = this.safeInteger (trade, 'timestamp');
|
||||
const id = this.safeString (trade, 'id');
|
||||
const marketId = this.safeString (trade, 'currencyPair');
|
||||
const symbol = this.safeSymbol (marketId, market);
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': undefined, // type
|
||||
'order': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'pair': market['id'],
|
||||
'maxCount': 128,
|
||||
};
|
||||
const response = await this.currentGetTransactionsPairMaxCount (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'currencyPair': this.marketId (symbol),
|
||||
'volume': amount,
|
||||
'price': price,
|
||||
'orderType': (side === 'buy') ? 0 : 1,
|
||||
};
|
||||
const response = await this.userPostAddOrder (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['result'],
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'orderId': id,
|
||||
};
|
||||
return await this.userPostCancelOrder (this.extend (request, params));
|
||||
}
|
||||
|
||||
sign (path, api = 'current', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'][api];
|
||||
if (api !== 'www') {
|
||||
url += '/' + api + '/' + this.implodeParams (path, params);
|
||||
}
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'current') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else if (api === 'user') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
const request = this.extend ({
|
||||
'token': this.apiKey,
|
||||
'nonce': nonce,
|
||||
}, query);
|
||||
const auth = nonce.toString () + '$' + this.apiKey;
|
||||
request['signature'] = this.hmac (this.encode (auth), this.encode (this.secret));
|
||||
body = this.json (request);
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'current', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if (typeof response !== 'string') {
|
||||
if ('errors' in response) {
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
Generated
Vendored
+345
@@ -0,0 +1,345 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinmarketcap extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinmarketcap',
|
||||
'name': 'CoinMarketCap',
|
||||
'rateLimit': 10000,
|
||||
'version': 'v1',
|
||||
'countries': [ 'US' ],
|
||||
'has': {
|
||||
'cancelOrder': false,
|
||||
'CORS': true,
|
||||
'createLimitOrder': false,
|
||||
'createMarketOrder': false,
|
||||
'createOrder': false,
|
||||
'editOrder': false,
|
||||
'privateAPI': false,
|
||||
'fetchBalance': false,
|
||||
'fetchCurrencies': true,
|
||||
'fetchL2OrderBook': false,
|
||||
'fetchMarkets': true,
|
||||
'fetchOHLCV': false,
|
||||
'fetchOrderBook': false,
|
||||
'fetchTicker': true,
|
||||
'fetchTickers': true,
|
||||
'fetchTrades': false,
|
||||
},
|
||||
'urls': {
|
||||
'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',
|
||||
'charts': 'https://graph.coinmarketcap.com',
|
||||
},
|
||||
'www': 'https://coinmarketcap.com',
|
||||
'doc': 'https://coinmarketcap.com/api',
|
||||
},
|
||||
'requiredCredentials': {
|
||||
'apiKey': false,
|
||||
'secret': false,
|
||||
},
|
||||
'api': {
|
||||
'files': {
|
||||
'get': [
|
||||
'generated/stats/global.json',
|
||||
],
|
||||
},
|
||||
'graphs': {
|
||||
'get': [
|
||||
'currencies/{name}/',
|
||||
],
|
||||
},
|
||||
'public': {
|
||||
'get': [
|
||||
'ticker/',
|
||||
'ticker/{id}/',
|
||||
'global/',
|
||||
],
|
||||
},
|
||||
},
|
||||
'currencyCodes': [
|
||||
'AUD',
|
||||
'BRL',
|
||||
'CAD',
|
||||
'CHF',
|
||||
'CNY',
|
||||
'EUR',
|
||||
'GBP',
|
||||
'HKD',
|
||||
'IDR',
|
||||
'INR',
|
||||
'JPY',
|
||||
'KRW',
|
||||
'MXN',
|
||||
'RUB',
|
||||
'USD',
|
||||
'BTC',
|
||||
'ETH',
|
||||
'LTC',
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
throw new ExchangeError ('Fetching order books is not supported by the API of ' + this.id);
|
||||
}
|
||||
|
||||
currencyCode (base, name) {
|
||||
const currencies = {
|
||||
'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)
|
||||
'Harvest Masternode Coin': 'Harvest Masternode Coin', // conflict with HC (HyperCash)
|
||||
'HOT Token': 'HOT Token',
|
||||
'Hydro Protocol': 'Hydro Protocol', // conflict with HOT (Holo)
|
||||
'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',
|
||||
'PutinCoin': 'PutinCoin', // conflict with PUT (Profile Utility Token)
|
||||
'Rcoin': 'Rcoin', // conflict with RCN (Ripio Credit Network)
|
||||
// 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)
|
||||
'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);
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const request = {
|
||||
'limit': 0,
|
||||
};
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
const result = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const market = response[i];
|
||||
const currencies = this.currencyCodes;
|
||||
for (let j = 0; j < currencies.length; j++) {
|
||||
const quote = currencies[j];
|
||||
const quoteId = quote.toLowerCase ();
|
||||
const baseId = market['id'];
|
||||
const base = this.currencyCode (market['symbol'], market['name']);
|
||||
const symbol = base + '/' + quote;
|
||||
const id = baseId + '/' + quoteId;
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'info': market,
|
||||
'active': undefined,
|
||||
'precision': this.precision,
|
||||
'limits': this.limits,
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchGlobal (currency = 'USD') {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
if (currency) {
|
||||
request['convert'] = currency;
|
||||
}
|
||||
return await this.publicGetGlobal (request);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
let timestamp = this.safeTimestamp (ticker, 'last_updated');
|
||||
if (timestamp === undefined) {
|
||||
timestamp = this.milliseconds ();
|
||||
}
|
||||
const change = this.safeFloat (ticker, 'percent_change_24h');
|
||||
let last = undefined;
|
||||
let symbol = undefined;
|
||||
let volume = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
const priceKey = 'price_' + market['quoteId'];
|
||||
last = this.safeFloat (ticker, priceKey);
|
||||
const volumeKey = '24h_volume_' + market['quoteId'];
|
||||
volume = this.safeFloat (ticker, volumeKey);
|
||||
}
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': undefined,
|
||||
'bidVolume': undefined,
|
||||
'ask': undefined,
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': change,
|
||||
'average': undefined,
|
||||
'baseVolume': undefined,
|
||||
'quoteVolume': volume,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTickers (currency = 'USD', params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'limit': 10000,
|
||||
};
|
||||
if (currency) {
|
||||
request['convert'] = currency;
|
||||
}
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
const result = {};
|
||||
for (let t = 0; t < response.length; t++) {
|
||||
const ticker = response[t];
|
||||
const currencyId = currency.toLowerCase ();
|
||||
const id = ticker['id'] + '/' + currencyId;
|
||||
let symbol = id;
|
||||
let market = undefined;
|
||||
if (id in this.markets_by_id) {
|
||||
market = this.markets_by_id[id];
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
result[symbol] = this.parseTicker (ticker, market);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'convert': market['quote'],
|
||||
'id': market['baseId'],
|
||||
};
|
||||
const response = await this.publicGetTickerId (this.extend (request, params));
|
||||
const ticker = response[0];
|
||||
return this.parseTicker (ticker, market);
|
||||
}
|
||||
|
||||
async fetchCurrencies (params = {}) {
|
||||
const request = {
|
||||
'limit': 0,
|
||||
};
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
const result = {};
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const currency = response[i];
|
||||
const id = this.safeString (currency, 'symbol');
|
||||
const name = this.safeString (currency, 'name');
|
||||
// todo: will need to rethink the fees
|
||||
// to add support for multiple withdrawal/deposit methods and
|
||||
// differentiated fees for each particular method
|
||||
const precision = 8; // default precision, todo: fix "magic constants"
|
||||
const code = this.currencyCode (id, name);
|
||||
result[code] = {
|
||||
'id': id,
|
||||
'code': code,
|
||||
'info': currency,
|
||||
'name': name,
|
||||
'active': true,
|
||||
'fee': undefined, // todo: redesign
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision),
|
||||
'max': Math.pow (10, precision),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision),
|
||||
'max': Math.pow (10, precision),
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'withdraw': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if ('error' in response) {
|
||||
if (response['error']) {
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+759
@@ -0,0 +1,759 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ArgumentsRequired, InvalidOrder, OrderNotFound, RateLimitExceeded, InsufficientFunds } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinmate extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinmate',
|
||||
'name': 'CoinMate',
|
||||
'countries': [ 'GB', 'CZ', 'EU' ], // UK, Czech Republic
|
||||
'rateLimit': 1000,
|
||||
'has': {
|
||||
'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/51840849/87460806-1c9f3f00-c616-11ea-8c46-a77018a8f3f4.jpg',
|
||||
'api': 'https://coinmate.io/api',
|
||||
'www': 'https://coinmate.io',
|
||||
'fees': 'https://coinmate.io/fees',
|
||||
'doc': [
|
||||
'https://coinmate.docs.apiary.io',
|
||||
'https://coinmate.io/developers',
|
||||
],
|
||||
'referral': 'https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0',
|
||||
},
|
||||
'requiredCredentials': {
|
||||
'apiKey': true,
|
||||
'secret': true,
|
||||
'uid': true,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'orderBook',
|
||||
'ticker',
|
||||
'transactions',
|
||||
'tradingPairs',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'balances',
|
||||
'bitcoinCashWithdrawal',
|
||||
'bitcoinCashDepositAddresses',
|
||||
'bitcoinDepositAddresses',
|
||||
'bitcoinWithdrawal',
|
||||
'bitcoinWithdrawalFees',
|
||||
'buyInstant',
|
||||
'buyLimit',
|
||||
'cancelOrder',
|
||||
'cancelOrderWithInfo',
|
||||
'createVoucher',
|
||||
'dashDepositAddresses',
|
||||
'dashWithdrawal',
|
||||
'ethereumWithdrawal',
|
||||
'ethereumDepositAddresses',
|
||||
'litecoinWithdrawal',
|
||||
'litecoinDepositAddresses',
|
||||
'openOrders',
|
||||
'order',
|
||||
'orderHistory',
|
||||
'orderById',
|
||||
'pusherAuth',
|
||||
'redeemVoucher',
|
||||
'replaceByBuyLimit',
|
||||
'replaceByBuyInstant',
|
||||
'replaceBySellLimit',
|
||||
'replaceBySellInstant',
|
||||
'rippleDepositAddresses',
|
||||
'rippleWithdrawal',
|
||||
'sellInstant',
|
||||
'sellLimit',
|
||||
'transactionHistory',
|
||||
'traderFees',
|
||||
'tradeHistory',
|
||||
'transfer',
|
||||
'transferHistory',
|
||||
'unconfirmedBitcoinDeposits',
|
||||
'unconfirmedBitcoinCashDeposits',
|
||||
'unconfirmedDashDeposits',
|
||||
'unconfirmedEthereumDeposits',
|
||||
'unconfirmedLitecoinDeposits',
|
||||
'unconfirmedRippleDeposits',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'percentage': true,
|
||||
'maker': 0.12 / 100,
|
||||
'taker': 0.25 / 100,
|
||||
'tiers': {
|
||||
'taker': [
|
||||
[0, 0.25 / 100],
|
||||
[10000, 0.23 / 100],
|
||||
[100000, 0.21 / 100],
|
||||
[250000, 0.20 / 100],
|
||||
[500000, 0.15 / 100],
|
||||
[1000000, 0.13 / 100],
|
||||
[3000000, 0.10 / 100],
|
||||
[15000000, 0.05 / 100],
|
||||
],
|
||||
'maker': [
|
||||
[0, 0.12 / 100],
|
||||
[10000, 0.11 / 100],
|
||||
[1000000, 0.10 / 100],
|
||||
[250000, 0.08 / 100],
|
||||
[500000, 0.05 / 100],
|
||||
[1000000, 0.03 / 100],
|
||||
[3000000, 0.02 / 100],
|
||||
[15000000, 0],
|
||||
],
|
||||
},
|
||||
},
|
||||
'promotional': {
|
||||
'trading': {
|
||||
'maker': 0.05 / 100,
|
||||
'taker': 0.15 / 100,
|
||||
'tiers': {
|
||||
'taker': [
|
||||
[0, 0.15 / 100],
|
||||
[10000, 0.14 / 100],
|
||||
[100000, 0.13 / 100],
|
||||
[250000, 0.12 / 100],
|
||||
[500000, 0.11 / 100],
|
||||
[1000000, 0.1 / 100],
|
||||
[3000000, 0.08 / 100],
|
||||
[15000000, 0.05 / 100],
|
||||
],
|
||||
'maker': [
|
||||
[0, 0.05 / 100],
|
||||
[10000, 0.04 / 100],
|
||||
[1000000, 0.03 / 100],
|
||||
[250000, 0.02 / 100],
|
||||
[500000, 0],
|
||||
[1000000, 0],
|
||||
[3000000, 0],
|
||||
[15000000, 0],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'options': {
|
||||
'promotionalMarkets': ['ETH/EUR', 'ETH/CZK', 'ETH/BTC', 'XRP/EUR', 'XRP/CZK', 'XRP/BTC', 'DASH/EUR', 'DASH/CZK', 'DASH/BTC', 'BCH/EUR', 'BCH/CZK', 'BCH/BTC'],
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {
|
||||
'No order with given ID': OrderNotFound,
|
||||
},
|
||||
'broad': {
|
||||
'Not enough account balance available': InsufficientFunds,
|
||||
'Incorrect order ID': InvalidOrder,
|
||||
'Minimum Order Size ': InvalidOrder,
|
||||
'TOO MANY REQUESTS': RateLimitExceeded,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetTradingPairs (params);
|
||||
//
|
||||
// {
|
||||
// "error":false,
|
||||
// "errorMessage":null,
|
||||
// "data": [
|
||||
// {
|
||||
// "name":"BTC_EUR",
|
||||
// "firstCurrency":"BTC",
|
||||
// "secondCurrency":"EUR",
|
||||
// "priceDecimals":2,
|
||||
// "lotDecimals":8,
|
||||
// "minAmount":0.0002,
|
||||
// "tradesWebSocketChannelId":"trades-BTC_EUR",
|
||||
// "orderBookWebSocketChannelId":"order_book-BTC_EUR",
|
||||
// "tradeStatisticsWebSocketChannelId":"statistics-BTC_EUR"
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data');
|
||||
const result = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const market = data[i];
|
||||
const id = this.safeString (market, 'name');
|
||||
const baseId = this.safeString (market, 'firstCurrency');
|
||||
const quoteId = this.safeString (market, 'secondCurrency');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const promotionalMarkets = this.safeValue (this.options, 'promotionalMarkets', []);
|
||||
let fees = this.safeValue (this.fees, 'trading');
|
||||
if (this.inArray (symbol, promotionalMarkets)) {
|
||||
const promotionalFees = this.safeValue (this.fees, 'promotional', {});
|
||||
fees = this.safeValue (promotionalFees, 'trading', fees);
|
||||
}
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': undefined,
|
||||
'maker': fees['maker'],
|
||||
'taker': fees['taker'],
|
||||
'info': market,
|
||||
'precision': {
|
||||
'price': this.safeInteger (market, 'priceDecimals'),
|
||||
'amount': this.safeInteger (market, 'lotDecimals'),
|
||||
},
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'minAmount'),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostBalances (params);
|
||||
const balances = this.safeValue (response, 'data');
|
||||
const result = { 'info': response };
|
||||
const currencyIds = Object.keys (balances);
|
||||
for (let i = 0; i < currencyIds.length; i++) {
|
||||
const currencyId = currencyIds[i];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const balance = this.safeValue (balances, currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (balance, 'available');
|
||||
account['used'] = this.safeFloat (balance, 'reserved');
|
||||
account['total'] = this.safeFloat (balance, 'balance');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'currencyPair': this.marketId (symbol),
|
||||
'groupByPriceLimit': 'False',
|
||||
};
|
||||
const response = await this.publicGetOrderBook (this.extend (request, params));
|
||||
const orderbook = response['data'];
|
||||
const timestamp = this.safeTimestamp (orderbook, 'timestamp');
|
||||
return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'currencyPair': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
const ticker = this.safeValue (response, 'data');
|
||||
const timestamp = this.safeTimestamp (ticker, 'timestamp');
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'vwap': undefined,
|
||||
'askVolume': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'amount'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'limit': 1000,
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['timestampFrom'] = since;
|
||||
}
|
||||
if (code !== undefined) {
|
||||
request['currency'] = this.currencyId (code);
|
||||
}
|
||||
const response = await this.privatePostTransferHistory (this.extend (request, params));
|
||||
const items = response['data'];
|
||||
return this.parseTransactions (items, undefined, since, limit);
|
||||
}
|
||||
|
||||
parseTransactionStatus (status) {
|
||||
const statuses = {
|
||||
// any other types ?
|
||||
'COMPLETED': 'ok',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseTransaction (item, currency = undefined) {
|
||||
//
|
||||
// deposits
|
||||
//
|
||||
// {
|
||||
// transactionId: 1862815,
|
||||
// timestamp: 1516803982388,
|
||||
// amountCurrency: 'LTC',
|
||||
// amount: 1,
|
||||
// fee: 0,
|
||||
// walletType: 'LTC',
|
||||
// transferType: 'DEPOSIT',
|
||||
// transferStatus: 'COMPLETED',
|
||||
// txid:
|
||||
// 'ccb9255dfa874e6c28f1a64179769164025329d65e5201849c2400abd6bce245',
|
||||
// destination: 'LQrtSKA6LnhcwRrEuiborQJnjFF56xqsFn',
|
||||
// destinationTag: null
|
||||
// }
|
||||
//
|
||||
// withdrawals
|
||||
//
|
||||
// {
|
||||
// transactionId: 2140966,
|
||||
// timestamp: 1519314282976,
|
||||
// amountCurrency: 'EUR',
|
||||
// amount: 8421.7228,
|
||||
// fee: 16.8772,
|
||||
// walletType: 'BANK_WIRE',
|
||||
// transferType: 'WITHDRAWAL',
|
||||
// transferStatus: 'COMPLETED',
|
||||
// txid: null,
|
||||
// destination: null,
|
||||
// destinationTag: null
|
||||
// }
|
||||
//
|
||||
const timestamp = this.safeInteger (item, 'timestamp');
|
||||
const amount = this.safeFloat (item, 'amount');
|
||||
const fee = this.safeFloat (item, 'fee');
|
||||
const txid = this.safeString (item, 'txid');
|
||||
const address = this.safeString (item, 'destination');
|
||||
const tag = this.safeString (item, 'destinationTag');
|
||||
const currencyId = this.safeString (item, 'amountCurrency');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const type = this.safeStringLower (item, 'transferType');
|
||||
const status = this.parseTransactionStatus (this.safeString (item, 'transferStatus'));
|
||||
const id = this.safeString (item, 'transactionId');
|
||||
return {
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'currency': code,
|
||||
'amount': amount,
|
||||
'type': type,
|
||||
'txid': txid,
|
||||
'address': address,
|
||||
'tag': tag,
|
||||
'status': status,
|
||||
'fee': {
|
||||
'cost': fee,
|
||||
'currency': code,
|
||||
},
|
||||
'info': item,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (limit === undefined) {
|
||||
limit = 1000;
|
||||
}
|
||||
const request = {
|
||||
'limit': limit,
|
||||
};
|
||||
if (symbol !== undefined) {
|
||||
const market = this.market (symbol);
|
||||
request['currencyPair'] = market['id'];
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['timestampFrom'] = since;
|
||||
}
|
||||
const response = await this.privatePostTradeHistory (this.extend (request, params));
|
||||
const items = response['data'];
|
||||
return this.parseTrades (items, undefined, since, limit);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
//
|
||||
// fetchMyTrades (private)
|
||||
//
|
||||
// {
|
||||
// transactionId: 2671819,
|
||||
// createdTimestamp: 1529649127605,
|
||||
// currencyPair: 'LTC_BTC',
|
||||
// type: 'BUY',
|
||||
// orderType: 'LIMIT',
|
||||
// orderId: 101810227,
|
||||
// amount: 0.01,
|
||||
// price: 0.01406,
|
||||
// fee: 0,
|
||||
// feeType: 'MAKER'
|
||||
// }
|
||||
//
|
||||
// fetchTrades (public)
|
||||
//
|
||||
// {
|
||||
// "timestamp":1561598833416,
|
||||
// "transactionId":"4156303",
|
||||
// "price":10950.41,
|
||||
// "amount":0.004,
|
||||
// "currencyPair":"BTC_EUR",
|
||||
// "tradeType":"BUY"
|
||||
// }
|
||||
//
|
||||
const marketId = this.safeString (trade, 'currencyPair');
|
||||
market = this.safeMarket (marketId, market, '_');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
const side = this.safeStringLower2 (trade, 'type', 'tradeType');
|
||||
const type = this.safeStringLower (trade, 'orderType');
|
||||
const orderId = this.safeString (trade, 'orderId');
|
||||
const id = this.safeString (trade, 'transactionId');
|
||||
const timestamp = this.safeInteger2 (trade, 'timestamp', 'createdTimestamp');
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (trade, 'fee');
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': market['quote'],
|
||||
};
|
||||
}
|
||||
let takerOrMaker = this.safeString (trade, 'feeType');
|
||||
takerOrMaker = (takerOrMaker === 'MAKER') ? 'maker' : 'taker';
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': market['symbol'],
|
||||
'type': type,
|
||||
'side': side,
|
||||
'order': orderId,
|
||||
'takerOrMaker': takerOrMaker,
|
||||
'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 = {
|
||||
'currencyPair': market['id'],
|
||||
'minutesIntoHistory': 10,
|
||||
};
|
||||
const response = await this.publicGetTransactions (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "error":false,
|
||||
// "errorMessage":null,
|
||||
// "data":[
|
||||
// {
|
||||
// "timestamp":1561598833416,
|
||||
// "transactionId":"4156303",
|
||||
// "price":10950.41,
|
||||
// "amount":0.004,
|
||||
// "currencyPair":"BTC_EUR",
|
||||
// "tradeType":"BUY"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseTrades (data, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const response = await this.privatePostOpenOrders (this.extend ({}, params));
|
||||
const extension = { 'status': 'open' };
|
||||
return this.parseOrders (response['data'], undefined, since, limit, extension);
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currencyPair': market['id'],
|
||||
};
|
||||
// offset param that appears in other parts of the API doesn't appear to be supported here
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.privatePostOrderHistory (this.extend (request, params));
|
||||
return this.parseOrders (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'FILLED': 'closed',
|
||||
'CANCELLED': 'canceled',
|
||||
'PARTIALLY_FILLED': 'open',
|
||||
'OPEN': 'open',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrderType (type) {
|
||||
const types = {
|
||||
'LIMIT': 'limit',
|
||||
'MARKET': 'market',
|
||||
};
|
||||
return this.safeString (types, type, type);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// limit sell
|
||||
//
|
||||
// {
|
||||
// id: 781246605,
|
||||
// timestamp: 1584480015133,
|
||||
// trailingUpdatedTimestamp: null,
|
||||
// type: 'SELL',
|
||||
// currencyPair: 'ETH_BTC',
|
||||
// price: 0.0345,
|
||||
// amount: 0.01,
|
||||
// stopPrice: null,
|
||||
// originalStopPrice: null,
|
||||
// marketPriceAtLastUpdate: null,
|
||||
// marketPriceAtOrderCreation: null,
|
||||
// orderTradeType: 'LIMIT',
|
||||
// hidden: false,
|
||||
// trailing: false,
|
||||
// clientOrderId: null
|
||||
// }
|
||||
//
|
||||
// limit buy
|
||||
//
|
||||
// {
|
||||
// id: 67527001,
|
||||
// timestamp: 1517931722613,
|
||||
// trailingUpdatedTimestamp: null,
|
||||
// type: 'BUY',
|
||||
// price: 5897.24,
|
||||
// remainingAmount: 0.002367,
|
||||
// originalAmount: 0.1,
|
||||
// stopPrice: null,
|
||||
// originalStopPrice: null,
|
||||
// marketPriceAtLastUpdate: null,
|
||||
// marketPriceAtOrderCreation: null,
|
||||
// status: 'CANCELLED',
|
||||
// orderTradeType: 'LIMIT',
|
||||
// hidden: false,
|
||||
// avgPrice: null,
|
||||
// trailing: false,
|
||||
// }
|
||||
//
|
||||
const id = this.safeString (order, 'id');
|
||||
const timestamp = this.safeInteger (order, 'timestamp');
|
||||
const side = this.safeStringLower (order, 'type');
|
||||
const price = this.safeFloat (order, 'price');
|
||||
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.max (amount - remaining, 0);
|
||||
if (remaining === 0) {
|
||||
status = 'closed';
|
||||
}
|
||||
if (price !== undefined) {
|
||||
cost = filled * price;
|
||||
}
|
||||
}
|
||||
const average = this.safeFloat (order, 'avgPrice');
|
||||
const marketId = this.safeString (order, 'currencyPair');
|
||||
const symbol = this.safeSymbol (marketId, market, '_');
|
||||
const clientOrderId = this.safeString (order, 'clientOrderId');
|
||||
const stopPrice = this.safeFloat (order, 'stopPrice');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': clientOrderId,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': stopPrice,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'average': average,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'trades': undefined,
|
||||
'info': order,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let method = 'privatePost' + this.capitalize (side);
|
||||
const request = {
|
||||
'currencyPair': this.marketId (symbol),
|
||||
};
|
||||
if (type === 'market') {
|
||||
if (side === 'buy') {
|
||||
request['total'] = this.amountToPrecision (symbol, amount); // amount in fiat
|
||||
} else {
|
||||
request['amount'] = this.amountToPrecision (symbol, amount); // amount in fiat
|
||||
}
|
||||
method += 'Instant';
|
||||
} else {
|
||||
request['amount'] = this.amountToPrecision (symbol, amount); // amount in crypto
|
||||
request['price'] = this.priceToPrecision (symbol, price);
|
||||
method += this.capitalize (type);
|
||||
}
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
const id = this.safeString (response, 'data');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'orderId': id,
|
||||
};
|
||||
let market = undefined;
|
||||
if (symbol) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const response = await this.privatePostOrderById (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data');
|
||||
return this.parseOrder (data, market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
// {"error":false,"errorMessage":null,"data":{"success":true,"remainingAmount":0.01}}
|
||||
const request = { 'orderId': id };
|
||||
const response = await this.privatePostCancelOrderWithInfo (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + path;
|
||||
if (api === 'public') {
|
||||
if (Object.keys (params).length) {
|
||||
url += '?' + this.urlencode (params);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
const auth = nonce + this.uid + this.apiKey;
|
||||
const signature = this.hmac (this.encode (auth), this.encode (this.secret));
|
||||
body = this.urlencode (this.extend ({
|
||||
'clientId': this.uid,
|
||||
'nonce': nonce,
|
||||
'publicKey': this.apiKey,
|
||||
'signature': signature.toUpperCase (),
|
||||
}, params));
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response !== undefined) {
|
||||
if ('error' in response) {
|
||||
// {"error":true,"errorMessage":"Minimum Order Size 0.01 ETH","data":null}
|
||||
if (response['error']) {
|
||||
const message = this.safeString (response, 'errorMessage');
|
||||
const feedback = this.id + ' ' + message;
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback);
|
||||
this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback);
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (code > 400) {
|
||||
if (body) {
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], body, feedback);
|
||||
this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback);
|
||||
throw new ExchangeError (feedback); // unknown message
|
||||
}
|
||||
throw new ExchangeError (this.id + ' ' + body);
|
||||
}
|
||||
}
|
||||
};
|
||||
+681
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, NotSupported } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinspot extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinspot',
|
||||
'name': 'CoinSpot',
|
||||
'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',
|
||||
'api': {
|
||||
'public': 'https://www.coinspot.com.au/pubapi',
|
||||
'private': 'https://www.coinspot.com.au/api',
|
||||
},
|
||||
'www': 'https://www.coinspot.com.au',
|
||||
'doc': 'https://www.coinspot.com.au/api',
|
||||
'referral': 'https://www.coinspot.com.au/register?code=PJURCU',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'latest',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'orders',
|
||||
'orders/history',
|
||||
'my/coin/deposit',
|
||||
'my/coin/send',
|
||||
'quote/buy',
|
||||
'quote/sell',
|
||||
'my/balances',
|
||||
'my/orders',
|
||||
'my/buy',
|
||||
'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',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/AUD': { 'id': 'btc', 'symbol': 'BTC/AUD', 'base': 'BTC', 'quote': 'AUD', 'baseId': 'btc', 'quoteId': 'aud' },
|
||||
'LTC/AUD': { 'id': 'ltc', 'symbol': 'LTC/AUD', 'base': 'LTC', 'quote': 'AUD', 'baseId': 'ltc', 'quoteId': 'aud' },
|
||||
'DOGE/AUD': { 'id': 'doge', 'symbol': 'DOGE/AUD', 'base': 'DOGE', 'quote': 'AUD', 'baseId': 'doge', 'quoteId': 'aud' },
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'DRK': 'DASH',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostMyBalances (params);
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'balance', {});
|
||||
const currencyIds = Object.keys (balances);
|
||||
for (let i = 0; i < currencyIds.length; i++) {
|
||||
const currencyId = currencyIds[i];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['total'] = this.safeFloat (balances, currencyId);
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'cointype': market['id'],
|
||||
};
|
||||
const orderbook = await this.privatePostOrders (this.extend (request, params));
|
||||
return this.parseOrderBook (orderbook, undefined, 'buyorders', 'sellorders', 'rate', 'amount');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetLatest (params);
|
||||
let id = this.marketId (symbol);
|
||||
id = id.toLowerCase ();
|
||||
const ticker = response['prices'][id];
|
||||
const timestamp = this.milliseconds ();
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': undefined,
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'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);
|
||||
if (type === 'market') {
|
||||
throw new ExchangeError (this.id + ' allows limit orders only');
|
||||
}
|
||||
const request = {
|
||||
'cointype': this.marketId (symbol),
|
||||
'amount': amount,
|
||||
'rate': price,
|
||||
};
|
||||
return await this[method] (this.extend (request, params));
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
throw new NotSupported (this.id + ' cancelOrder () is not fully implemented yet');
|
||||
// let method = 'privatePostMyBuy';
|
||||
// return await this[method] ({ 'id': id });
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
if (!this.apiKey) {
|
||||
throw new AuthenticationError (this.id + ' requires apiKey for all requests');
|
||||
}
|
||||
const url = this.urls['api'][api] + '/' + path;
|
||||
if (api === 'private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
body = this.json (this.extend ({ 'nonce': nonce }, params));
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'key': this.apiKey,
|
||||
'sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'),
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+1308
File diff suppressed because it is too large.
Load diff
Generated
Vendored
+1119
File diff suppressed because it is too large.
Load diff
+1785
File diff suppressed because it is too large.
Load diff
+1497
File diff suppressed because it is too large.
Load diff
+1273
File diff suppressed because it is too large.
Load diff
+1141
File diff suppressed because it is too large.
Load diff
+1591
File diff suppressed because it is too large.
Load diff
+463
@@ -0,0 +1,463 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, ExchangeNotAvailable, ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class exx extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'exx',
|
||||
'name': 'EXX',
|
||||
'countries': [ 'CN' ],
|
||||
'rateLimit': 1000 / 10,
|
||||
'userAgent': this.userAgents['chrome'],
|
||||
'has': {
|
||||
'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',
|
||||
'api': {
|
||||
'public': 'https://api.exx.com/data/v1',
|
||||
'private': 'https://trade.exx.com/api',
|
||||
},
|
||||
'www': 'https://www.exx.com/',
|
||||
'doc': 'https://www.exx.com/help/restApi',
|
||||
'fees': 'https://www.exx.com/help/rate',
|
||||
'referral': 'https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'markets',
|
||||
'tickers',
|
||||
'ticker',
|
||||
'depth',
|
||||
'trades',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'order',
|
||||
'cancel',
|
||||
'getOrder',
|
||||
'getOpenOrders',
|
||||
'getBalance',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.1 / 100,
|
||||
'taker': 0.1 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'BCC': 0.0003,
|
||||
'BCD': 0.0,
|
||||
'BOT': 10.0,
|
||||
'BTC': 0.001,
|
||||
'BTG': 0.0,
|
||||
'BTM': 25.0,
|
||||
'BTS': 3.0,
|
||||
'EOS': 1.0,
|
||||
'ETC': 0.01,
|
||||
'ETH': 0.01,
|
||||
'ETP': 0.012,
|
||||
'HPY': 0.0,
|
||||
'HSR': 0.001,
|
||||
'INK': 20.0,
|
||||
'LTC': 0.005,
|
||||
'MCO': 0.6,
|
||||
'MONA': 0.01,
|
||||
'QASH': 5.0,
|
||||
'QCASH': 5.0,
|
||||
'QTUM': 0.01,
|
||||
'USDT': 5.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'DOS': 'DEMOS',
|
||||
'TV': 'TIV', // Ti-Value
|
||||
},
|
||||
'exceptions': {
|
||||
'103': AuthenticationError,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetMarkets (params);
|
||||
const ids = Object.keys (response);
|
||||
const result = [];
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
const market = response[id];
|
||||
const [ baseId, quoteId ] = id.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const active = market['isOpen'] === true;
|
||||
const precision = {
|
||||
'amount': parseInt (market['amountScale']),
|
||||
'price': parseInt (market['priceScale']),
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision['amount']),
|
||||
'max': Math.pow (10, precision['amount']),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': Math.pow (10, precision['price']),
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const symbol = market['symbol'];
|
||||
const timestamp = this.safeInteger (ticker, 'date');
|
||||
ticker = ticker['ticker'];
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': this.safeFloat (ticker, 'riseRate'),
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'vol'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currency': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
return this.parseTicker (response, market);
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetTickers (params);
|
||||
const result = {};
|
||||
const timestamp = this.milliseconds ();
|
||||
const ids = Object.keys (response);
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
if (!(id in this.marketsById)) {
|
||||
continue;
|
||||
}
|
||||
const market = this.marketsById[id];
|
||||
const symbol = market['symbol'];
|
||||
const ticker = {
|
||||
'date': timestamp,
|
||||
'ticker': response[id],
|
||||
};
|
||||
result[symbol] = this.parseTicker (ticker, market);
|
||||
}
|
||||
return this.filterByArray (result, 'symbol', symbols);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'currency': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetDepth (this.extend (request, params));
|
||||
const timestamp = this.safeTimestamp (response, 'timestamp');
|
||||
return this.parseOrderBook (response, timestamp);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'date');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const type = 'limit';
|
||||
const side = this.safeString (trade, 'type');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': undefined,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currency': market['id'],
|
||||
};
|
||||
const response = await this.publicGetTrades (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetGetBalance (params);
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'funds');
|
||||
const currencies = Object.keys (balances);
|
||||
for (let i = 0; i < currencies.length; i++) {
|
||||
const currencyId = currencies[i];
|
||||
const balance = balances[currencyId];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = {
|
||||
'free': this.safeFloat (balance, 'balance'),
|
||||
'used': this.safeFloat (balance, 'freeze'),
|
||||
'total': this.safeFloat (balance, 'total'),
|
||||
};
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// {
|
||||
// "fees": 0,
|
||||
// "total_amount": 1,
|
||||
// "trade_amount": 0,
|
||||
// "price": 30,
|
||||
// "currency": “eth_hsr",
|
||||
// "id": "13878",
|
||||
// "trade_money": 0,
|
||||
// "type": "buy",
|
||||
// "trade_date": 1509728897772,
|
||||
// "status": 0
|
||||
// }
|
||||
//
|
||||
const symbol = market['symbol'];
|
||||
const timestamp = parseInt (order['trade_date']);
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const cost = this.safeFloat (order, 'trade_money');
|
||||
const amount = this.safeFloat (order, 'total_amount');
|
||||
const filled = this.safeFloat (order, 'trade_amount', 0.0);
|
||||
const remaining = parseFloat (this.amountToPrecision (symbol, amount - filled));
|
||||
let status = this.safeInteger (order, 'status');
|
||||
if (status === 1) {
|
||||
status = 'canceled';
|
||||
} else if (status === 2) {
|
||||
status = 'closed';
|
||||
} else {
|
||||
status = 'open';
|
||||
}
|
||||
let fee = undefined;
|
||||
if ('fees' in order) {
|
||||
fee = {
|
||||
'cost': this.safeFloat (order, 'fees'),
|
||||
'currency': market['quote'],
|
||||
};
|
||||
}
|
||||
return {
|
||||
'id': this.safeString (order, 'id'),
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'timeInForce': undefined,
|
||||
'side': order['type'],
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'trades': undefined,
|
||||
'fee': fee,
|
||||
'info': order,
|
||||
'average': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currency': market['id'],
|
||||
'type': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
};
|
||||
const response = await this.privateGetOrder (this.extend (request, params));
|
||||
const id = this.safeString (response, 'id');
|
||||
const order = this.parseOrder ({
|
||||
'id': id,
|
||||
'trade_date': this.milliseconds (),
|
||||
'total_amount': amount,
|
||||
'price': price,
|
||||
'type': side,
|
||||
'info': response,
|
||||
}, market);
|
||||
return order;
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': id,
|
||||
'currency': market['id'],
|
||||
};
|
||||
const response = await this.privateGetCancel (this.extend (request, params));
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': id,
|
||||
'currency': market['id'],
|
||||
};
|
||||
const response = await this.privateGetGetOrder (this.extend (request, params));
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
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'],
|
||||
};
|
||||
const response = await this.privateGetGetOpenOrders (this.extend (request, params));
|
||||
if (!Array.isArray (response)) {
|
||||
return [];
|
||||
}
|
||||
return this.parseOrders (response, market, since, limit);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'][api] + '/' + path;
|
||||
if (api === 'public') {
|
||||
if (Object.keys (params).length) {
|
||||
url += '?' + this.urlencode (params);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const query = this.urlencode (this.keysort (this.extend ({
|
||||
'accesskey': this.apiKey,
|
||||
'nonce': this.nonce (),
|
||||
}, params)));
|
||||
const signed = this.hmac (this.encode (query), this.encode (this.secret), 'sha512');
|
||||
url += '?' + query + '&signature=' + signed;
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
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
|
||||
}
|
||||
//
|
||||
// {"result":false,"message":"服务端忙碌"}
|
||||
// ... and other formats
|
||||
//
|
||||
const code = this.safeString (response, 'code');
|
||||
const message = this.safeString (response, 'message');
|
||||
const feedback = this.id + ' ' + body;
|
||||
if (code === '100') {
|
||||
return;
|
||||
}
|
||||
if (code !== undefined) {
|
||||
this.throwExactlyMatchedException (this.exceptions, code, feedback);
|
||||
if (code === '308') {
|
||||
// this is returned by the exchange when there are no open orders
|
||||
// {"code":308,"message":"Not Found Transaction Record"}
|
||||
return;
|
||||
} else {
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
const result = this.safeValue (response, 'result');
|
||||
if (result !== undefined) {
|
||||
if (!result) {
|
||||
if (message === '服务端忙碌') {
|
||||
throw new ExchangeNotAvailable (feedback);
|
||||
} else {
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+742
@@ -0,0 +1,742 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { BadSymbol, ExchangeError, ExchangeNotAvailable, ArgumentsRequired, InsufficientFunds, InvalidOrder, RateLimitExceeded, InvalidNonce, AuthenticationError, NotSupported } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class fcoin extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'fcoin',
|
||||
'name': 'FCoin',
|
||||
'countries': [ 'CN' ],
|
||||
'rateLimit': 2000,
|
||||
'userAgent': this.userAgents['chrome39'],
|
||||
'version': 'v2',
|
||||
'accounts': undefined,
|
||||
'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,
|
||||
'fetchOrder': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchOrderBooks': false,
|
||||
'fetchOrders': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTime': true,
|
||||
'fetchTrades': true,
|
||||
'fetchTradingLimits': false,
|
||||
'withdraw': false,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': 'M1',
|
||||
'3m': 'M3',
|
||||
'5m': 'M5',
|
||||
'15m': 'M15',
|
||||
'30m': 'M30',
|
||||
'1h': 'H1',
|
||||
'1d': 'D1',
|
||||
'1w': 'W1',
|
||||
'1M': 'MN',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/42244210-c8c42e1e-7f1c-11e8-8710-a5fb63b165c4.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.{hostname}',
|
||||
'private': 'https://api.{hostname}',
|
||||
'market': 'https://api.{hostname}',
|
||||
'openapi': 'https://www.{hostname}',
|
||||
},
|
||||
'www': 'https://www.fcoin.com',
|
||||
'referral': 'https://www.fcoin.com/i/Z5P7V',
|
||||
'doc': 'https://developer.fcoin.com',
|
||||
'fees': 'https://fcoinjp.zendesk.com/hc/en-us/articles/360018727371',
|
||||
},
|
||||
'api': {
|
||||
'openapi': {
|
||||
'get': [
|
||||
'symbols',
|
||||
],
|
||||
},
|
||||
'market': {
|
||||
'get': [
|
||||
'ticker/{symbol}',
|
||||
'depth/{level}/{symbol}',
|
||||
'trades/{symbol}',
|
||||
'candles/{timeframe}/{symbol}',
|
||||
],
|
||||
},
|
||||
'public': {
|
||||
'get': [
|
||||
'symbols',
|
||||
'currencies',
|
||||
'server-time',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'accounts/balance',
|
||||
'assets/accounts/balance',
|
||||
'broker/otc/suborders',
|
||||
'broker/otc/suborders/{id}',
|
||||
'broker/otc/suborders/{id}/payments',
|
||||
'broker/otc/users',
|
||||
'broker/otc/users/me/balances',
|
||||
'broker/otc/users/me/balance',
|
||||
'broker/leveraged_accounts/account',
|
||||
'broker/leveraged_accounts',
|
||||
'orders',
|
||||
'orders/{order_id}',
|
||||
'orders/{order_id}/match-results', // check order result
|
||||
],
|
||||
'post': [
|
||||
'assets/accounts/assets-to-spot',
|
||||
'accounts/spot-to-assets',
|
||||
'broker/otc/assets/transfer/in',
|
||||
'broker/otc/assets/transfer/out',
|
||||
'broker/otc/suborders',
|
||||
'broker/otc/suborders/{id}/pay_confirm',
|
||||
'broker/otc/suborders/{id}/cancel',
|
||||
'broker/leveraged/assets/transfer/in',
|
||||
'broker/leveraged/assets/transfer/out',
|
||||
'orders',
|
||||
'orders/{order_id}/submit-cancel', // cancel order
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'maker': -0.0002,
|
||||
'taker': 0.0003,
|
||||
},
|
||||
},
|
||||
'limits': {
|
||||
'amount': { 'min': 0.01, 'max': 100000 },
|
||||
},
|
||||
'options': {
|
||||
'createMarketBuyOrderRequiresPrice': true,
|
||||
'fetchMarketsMethod': 'fetch_markets_from_open_api', // or 'fetch_markets_from_api'
|
||||
'limits': {
|
||||
'BTM/USDT': { 'amount': { 'min': 0.1, 'max': 10000000 }},
|
||||
'ETC/USDT': { 'amount': { 'min': 0.001, 'max': 400000 }},
|
||||
'ETH/USDT': { 'amount': { 'min': 0.001, 'max': 10000 }},
|
||||
'LTC/USDT': { 'amount': { 'min': 0.001, 'max': 40000 }},
|
||||
'BCH/USDT': { 'amount': { 'min': 0.001, 'max': 5000 }},
|
||||
'BTC/USDT': { 'amount': { 'min': 0.001, 'max': 1000 }},
|
||||
'ICX/ETH': { 'amount': { 'min': 0.01, 'max': 3000000 }},
|
||||
'OMG/ETH': { 'amount': { 'min': 0.01, 'max': 500000 }},
|
||||
'FT/USDT': { 'amount': { 'min': 1, 'max': 10000000 }},
|
||||
'ZIL/ETH': { 'amount': { 'min': 1, 'max': 10000000 }},
|
||||
'ZIP/ETH': { 'amount': { 'min': 1, 'max': 10000000 }},
|
||||
'FT/BTC': { 'amount': { 'min': 1, 'max': 10000000 }},
|
||||
'FT/ETH': { 'amount': { 'min': 1, 'max': 10000000 }},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'400': NotSupported, // Bad Request
|
||||
'401': AuthenticationError,
|
||||
'405': NotSupported,
|
||||
'429': RateLimitExceeded, // Too Many Requests, exceed api request limit
|
||||
'1002': ExchangeNotAvailable, // System busy
|
||||
'1016': InsufficientFunds,
|
||||
'2136': AuthenticationError, // The API key is expired
|
||||
'3008': InvalidOrder,
|
||||
'6004': InvalidNonce,
|
||||
'6005': AuthenticationError, // Illegal API Signature
|
||||
'40003': BadSymbol,
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'DAG': 'DAGX',
|
||||
'PAI': 'PCHAIN',
|
||||
'MT': 'Mariana Token',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const method = this.safeString (this.options, 'fetchMarketsMethod', 'fetch_markets_from_open_api');
|
||||
return await this[method] (params);
|
||||
}
|
||||
|
||||
async fetchMarketsFromOpenAPI (params = {}) {
|
||||
// https://github.com/ccxt/ccxt/issues/5648
|
||||
const response = await this.openapiGetSymbols (params);
|
||||
//
|
||||
// {
|
||||
// "status":"ok",
|
||||
// "data":{
|
||||
// "categories":[ "fone::coinforce", ... ],
|
||||
// "symbols":{
|
||||
// "mdaeth":{
|
||||
// "price_decimal":8,
|
||||
// "amount_decimal":2,
|
||||
// "base_currency":"mda",
|
||||
// "quote_currency":"eth",
|
||||
// "symbol":"mdaeth",
|
||||
// "category":"fone::bitangel",
|
||||
// "leveraged_multiple":null,
|
||||
// "tradeable":false,
|
||||
// "market_order_enabled":false,
|
||||
// "limit_amount_min":"1",
|
||||
// "limit_amount_max":"10000000",
|
||||
// "main_tag":"",
|
||||
// "daily_open_at":"",
|
||||
// "daily_close_at":""
|
||||
// },
|
||||
// }
|
||||
// "category_ref":{
|
||||
// "fone::coinforce":[ "btcusdt", ... ],
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data', {});
|
||||
const markets = this.safeValue (data, 'symbols', {});
|
||||
const keys = Object.keys (markets);
|
||||
const result = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const market = markets[key];
|
||||
const id = this.safeString (market, '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 symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'price': this.safeInteger (market, 'price_decimal'),
|
||||
'amount': this.safeInteger (market, 'amount_decimal'),
|
||||
};
|
||||
const limits = {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'limit_amount_min'),
|
||||
'max': this.safeFloat (market, 'limit_amount_max'),
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': Math.pow (10, precision['price']),
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
};
|
||||
const active = this.safeValue (market, 'tradeable', false);
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'precision': precision,
|
||||
'limits': limits,
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchMarketsFromAPI (params = {}) {
|
||||
const response = await this.publicGetSymbols (params);
|
||||
//
|
||||
// {
|
||||
// "status":0,
|
||||
// "data":[
|
||||
// {
|
||||
// "name":"dapusdt",
|
||||
// "base_currency":"dap",
|
||||
// "quote_currency":"usdt",
|
||||
// "price_decimal":6,
|
||||
// "amount_decimal":2,
|
||||
// "tradable":true
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const result = [];
|
||||
const markets = this.safeValue (response, 'data');
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const id = this.safeString (market, 'name');
|
||||
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 symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'price': market['price_decimal'],
|
||||
'amount': market['amount_decimal'],
|
||||
};
|
||||
let limits = {
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': Math.pow (10, precision['price']),
|
||||
},
|
||||
};
|
||||
const active = this.safeValue (market, 'tradable', false);
|
||||
if (symbol in this.options['limits']) {
|
||||
limits = this.extend (this.options['limits'][symbol], limits);
|
||||
}
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'precision': precision,
|
||||
'limits': limits,
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetAccountsBalance (params);
|
||||
const result = { 'info': response };
|
||||
const balances = this.safeValue (response, 'data');
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[i];
|
||||
const currencyId = this.safeString (balance, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (balance, 'available');
|
||||
account['total'] = this.safeFloat (balance, 'balance');
|
||||
account['used'] = this.safeFloat (balance, 'frozen');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseBidsAsks (orders, priceKey = 0, amountKey = 1) {
|
||||
const result = [];
|
||||
const length = orders.length;
|
||||
const halfLength = parseInt (length / 2);
|
||||
// += 2 in the for loop below won't transpile
|
||||
for (let i = 0; i < halfLength; i++) {
|
||||
const index = i * 2;
|
||||
const priceField = this.sum (index, priceKey);
|
||||
const amountField = this.sum (index, amountKey);
|
||||
result.push ([
|
||||
this.safeFloat (orders, priceField),
|
||||
this.safeFloat (orders, amountField),
|
||||
]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (limit !== undefined) {
|
||||
if ((limit === 20) || (limit === 150)) {
|
||||
limit = 'L' + limit.toString ();
|
||||
} else {
|
||||
throw new ExchangeError (this.id + ' fetchOrderBook supports limit of 20 or 150. Other values are not accepted');
|
||||
}
|
||||
} else {
|
||||
limit = 'L20';
|
||||
}
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'level': limit, // L20, L150
|
||||
};
|
||||
const response = await this.marketGetDepthLevelSymbol (this.extend (request, params));
|
||||
const orderbook = this.safeValue (response, 'data');
|
||||
return this.parseOrderBook (orderbook, orderbook['ts'], 'bids', 'asks', 0, 1);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const ticker = await this.marketGetTickerSymbol (this.extend (request, params));
|
||||
return this.parseTicker (ticker['data'], market);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = undefined;
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const tickerType = this.safeString (ticker, 'type');
|
||||
if (tickerType !== undefined) {
|
||||
const parts = tickerType.split ('.');
|
||||
const id = parts[1];
|
||||
symbol = this.safeSymbol (id, market);
|
||||
}
|
||||
}
|
||||
const values = ticker['ticker'];
|
||||
const last = this.safeFloat (values, 0);
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (values, 7),
|
||||
'low': this.safeFloat (values, 8),
|
||||
'bid': this.safeFloat (values, 2),
|
||||
'bidVolume': this.safeFloat (values, 3),
|
||||
'ask': this.safeFloat (values, 4),
|
||||
'askVolume': this.safeFloat (values, 5),
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (values, 9),
|
||||
'quoteVolume': this.safeFloat (values, 10),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeInteger (trade, 'ts');
|
||||
const side = this.safeStringLower (trade, 'side');
|
||||
const id = this.safeString (trade, 'id');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
const fee = undefined;
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'order': undefined,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'limit': limit,
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['timestamp'] = parseInt (since / 1000);
|
||||
}
|
||||
const response = await this.marketGetTradesSymbol (this.extend (request, params));
|
||||
return this.parseTrades (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
'side': side,
|
||||
'type': type,
|
||||
};
|
||||
// for market buy it requires the amount of quote currency to spend
|
||||
if ((type === 'market') && (side === 'buy')) {
|
||||
if (this.options['createMarketBuyOrderRequiresPrice']) {
|
||||
if (price === undefined) {
|
||||
throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)");
|
||||
} else {
|
||||
request['amount'] = this.costToPrecision (symbol, amount * price);
|
||||
}
|
||||
} else {
|
||||
request['amount'] = this.costToPrecision (symbol, amount);
|
||||
}
|
||||
} else {
|
||||
request['amount'] = this.amountToPrecision (symbol, amount);
|
||||
}
|
||||
if ((type === 'limit') || (type === 'ioc') || (type === 'fok')) {
|
||||
request['price'] = this.priceToPrecision (symbol, price);
|
||||
}
|
||||
const response = await this.privatePostOrders (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['data'],
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
const response = await this.privatePostOrdersOrderIdSubmitCancel (this.extend (request, params));
|
||||
const order = this.parseOrder (response);
|
||||
return this.extend (order, {
|
||||
'id': id,
|
||||
'status': 'canceled',
|
||||
});
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'submitted': 'open',
|
||||
'canceled': 'canceled',
|
||||
'partial_filled': 'open',
|
||||
'partial_canceled': 'canceled',
|
||||
'filled': 'closed',
|
||||
'pending_cancel': 'canceled',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// {
|
||||
// "id": "string",
|
||||
// "symbol": "string",
|
||||
// "type": "limit",
|
||||
// "side": "buy",
|
||||
// "price": "string",
|
||||
// "amount": "string",
|
||||
// "state": "submitted",
|
||||
// "executed_value": "string",
|
||||
// "fill_fees": "string",
|
||||
// "filled_amount": "string",
|
||||
// "created_at": 0,
|
||||
// "source": "web"
|
||||
// }
|
||||
//
|
||||
const id = this.safeString (order, 'id');
|
||||
const side = this.safeString (order, 'side');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'state'));
|
||||
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');
|
||||
const filled = this.safeFloat (order, 'filled_amount');
|
||||
let remaining = undefined;
|
||||
let price = this.safeFloat (order, 'price');
|
||||
let cost = this.safeFloat (order, 'executed_value');
|
||||
if (filled !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
remaining = amount - filled;
|
||||
}
|
||||
if (cost === undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * filled;
|
||||
}
|
||||
} else if ((cost > 0) && (filled > 0)) {
|
||||
price = cost / filled;
|
||||
}
|
||||
}
|
||||
let feeCurrency = undefined;
|
||||
let feeCost = undefined;
|
||||
const feeRebate = this.safeFloat (order, 'fees_income');
|
||||
if ((feeRebate !== undefined) && (feeRebate > 0)) {
|
||||
if (market !== undefined) {
|
||||
feeCurrency = (side === 'buy') ? market['quote'] : market['base'];
|
||||
}
|
||||
feeCost = -feeRebate;
|
||||
} else {
|
||||
feeCost = this.safeFloat (order, 'fill_fees');
|
||||
if (market !== undefined) {
|
||||
feeCurrency = (side === 'buy') ? market['base'] : market['quote'];
|
||||
}
|
||||
}
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': orderType,
|
||||
'timeInForce': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'stopPrice': undefined,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'remaining': remaining,
|
||||
'filled': filled,
|
||||
'average': undefined,
|
||||
'status': status,
|
||||
'fee': {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrency,
|
||||
},
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
const response = await this.privateGetOrdersOrderId (this.extend (request, params));
|
||||
return this.parseOrder (response['data']);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const request = { 'states': 'submitted,partial_filled' };
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const request = { 'states': 'partial_canceled,filled' };
|
||||
return await this.fetchOrders (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOrders() requires a `symbol` argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'states': 'submitted,partial_filled,partial_canceled,filled,canceled',
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.privateGetOrders (this.extend (request, params));
|
||||
return this.parseOrders (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined) {
|
||||
return [
|
||||
this.safeTimestamp (ohlcv, 'id'),
|
||||
this.safeFloat (ohlcv, 'open'),
|
||||
this.safeFloat (ohlcv, 'high'),
|
||||
this.safeFloat (ohlcv, 'low'),
|
||||
this.safeFloat (ohlcv, 'close'),
|
||||
this.safeFloat (ohlcv, 'base_vol'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
if (limit === undefined) {
|
||||
limit = 20; // default is 20
|
||||
}
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'timeframe': this.timeframes[timeframe],
|
||||
'limit': limit,
|
||||
};
|
||||
if (since !== undefined) {
|
||||
const sinceInSeconds = parseInt (since / 1000);
|
||||
const timerange = limit * this.parseTimeframe (timeframe);
|
||||
request['before'] = this.sum (sinceInSeconds, timerange) - 1;
|
||||
}
|
||||
const response = await this.marketGetCandlesTimeframeSymbol (this.extend (request, params));
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseOHLCVs (data, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let request = '/';
|
||||
const openAPI = (api === 'openapi');
|
||||
const privateAPI = (api === 'private');
|
||||
request += openAPI ? (api + '/') : '';
|
||||
request += this.version + '/';
|
||||
request += (privateAPI || openAPI) ? '' : (api + '/');
|
||||
request += this.implodeParams (path, params);
|
||||
let query = this.omit (params, this.extractParams (path));
|
||||
let url = this.implodeParams (this.urls['api'][api], {
|
||||
'hostname': this.hostname,
|
||||
});
|
||||
url += request;
|
||||
if (privateAPI) {
|
||||
this.checkRequiredCredentials ();
|
||||
const timestamp = this.nonce ().toString ();
|
||||
query = this.keysort (query);
|
||||
if (method === 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.rawencode (query);
|
||||
}
|
||||
}
|
||||
// HTTP_METHOD + HTTP_REQUEST_URI + TIMESTAMP + POST_BODY
|
||||
let auth = method + url + timestamp;
|
||||
if (method === 'POST') {
|
||||
if (Object.keys (query).length) {
|
||||
body = this.json (query);
|
||||
auth += this.urlencode (query);
|
||||
}
|
||||
}
|
||||
const payload = this.stringToBase64 (auth);
|
||||
let signature = this.hmac (payload, this.encode (this.secret), 'sha1', 'binary');
|
||||
signature = this.decode (this.stringToBase64 (signature));
|
||||
headers = {
|
||||
'FC-ACCESS-KEY': this.apiKey,
|
||||
'FC-ACCESS-SIGNATURE': signature,
|
||||
'FC-ACCESS-TIMESTAMP': timestamp,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
} else {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return; // fallback to default error handler
|
||||
}
|
||||
const status = this.safeString (response, 'status');
|
||||
if (status !== '0' && status !== 'ok') {
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwExactlyMatchedException (this.exceptions, status, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const fcoin = require ('./fcoin.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class fcoinjp extends fcoin {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'fcoinjp',
|
||||
'name': 'FCoinJP',
|
||||
'countries': [ 'JP' ],
|
||||
'hostname': 'fcoinjp.com',
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/54219174-08b66b00-4500-11e9-862d-f522d0fe08c6.jpg',
|
||||
'fees': 'https://fcoinjp.zendesk.com/hc/en-us/articles/360018727371',
|
||||
'www': 'https://www.fcoinjp.com',
|
||||
'referral': undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class flowbtc extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'flowbtc',
|
||||
'name': 'flowBTC',
|
||||
'countries': [ 'BR' ], // Brazil
|
||||
'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/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',
|
||||
},
|
||||
'requiredCredentials': {
|
||||
'apiKey': true,
|
||||
'secret': true,
|
||||
'uid': true,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'post': [
|
||||
'GetTicker',
|
||||
'GetTrades',
|
||||
'GetTradesByDate',
|
||||
'GetOrderBook',
|
||||
'GetProductPairs',
|
||||
'GetProducts',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'CreateAccount',
|
||||
'GetUserInfo',
|
||||
'SetUserInfo',
|
||||
'GetAccountInfo',
|
||||
'GetAccountTrades',
|
||||
'GetDepositAddresses',
|
||||
'Withdraw',
|
||||
'CreateOrder',
|
||||
'ModifyOrder',
|
||||
'CancelOrder',
|
||||
'CancelAllOrders',
|
||||
'GetAccountOpenOrders',
|
||||
'GetOrderFee',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'maker': 0.0025,
|
||||
'taker': 0.005,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicPostGetProductPairs (params);
|
||||
const markets = this.safeValue (response, 'productPairs');
|
||||
const result = {};
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const id = this.safeString (market, 'name');
|
||||
const baseId = this.safeString (market, 'product1Label');
|
||||
const quoteId = this.safeString (market, 'product2Label');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const precision = {
|
||||
'amount': this.safeInteger (market, 'product1DecimalPlaces'),
|
||||
'price': this.safeInteger (market, 'product2DecimalPlaces'),
|
||||
};
|
||||
const symbol = base + '/' + quote;
|
||||
result[symbol] = {
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': market,
|
||||
'active': undefined,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostGetAccountInfo (params);
|
||||
const balances = this.safeValue (response, 'currencies');
|
||||
const result = { 'info': response };
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[i];
|
||||
const currencyId = balance['name'];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (balance, 'balance');
|
||||
account['total'] = this.safeFloat (balance, 'hold');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'productPair': market['id'],
|
||||
};
|
||||
const response = await this.publicPostGetOrderBook (this.extend (request, params));
|
||||
return this.parseOrderBook (response, undefined, 'bids', 'asks', 'px', 'qty');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'productPair': market['id'],
|
||||
};
|
||||
const ticker = await this.publicPostGetTicker (this.extend (request, params));
|
||||
const timestamp = this.milliseconds ();
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'bid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'ask'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'volume24hr'),
|
||||
'quoteVolume': this.safeFloat (ticker, 'volume24hrProduct2'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market) {
|
||||
const timestamp = this.safeTimestamp (trade, 'unixtime');
|
||||
const side = (trade['incomingOrderSide'] === 0) ? 'buy' : 'sell';
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const price = this.safeFloat (trade, 'px');
|
||||
const amount = this.safeFloat (trade, 'qty');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
return {
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': market['symbol'],
|
||||
'id': id,
|
||||
'order': undefined,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'takerOrMaker': undefined,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'ins': market['id'],
|
||||
'startIndex': -1,
|
||||
};
|
||||
const response = await this.publicPostGetTrades (this.extend (request, params));
|
||||
return this.parseTrades (response['trades'], market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const orderType = (type === 'market') ? 1 : 0;
|
||||
const request = {
|
||||
'ins': this.marketId (symbol),
|
||||
'side': side,
|
||||
'orderType': orderType,
|
||||
'qty': amount,
|
||||
'px': this.priceToPrecision (symbol, price),
|
||||
};
|
||||
const response = await this.privatePostCreateOrder (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['serverOrderId'],
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if ('ins' in params) {
|
||||
const request = {
|
||||
'serverOrderId': id,
|
||||
};
|
||||
return await this.privatePostCancelOrder (this.extend (request, params));
|
||||
}
|
||||
throw new ExchangeError (this.id + ' requires `ins` symbol parameter for cancelling an order');
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const url = this.urls['api'] + '/' + this.version + '/' + path;
|
||||
if (api === 'public') {
|
||||
if (Object.keys (params).length) {
|
||||
body = this.json (params);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
const auth = nonce.toString () + this.uid + this.apiKey;
|
||||
const signature = this.hmac (this.encode (auth), this.encode (this.secret));
|
||||
body = this.json (this.extend ({
|
||||
'apiKey': this.apiKey,
|
||||
'apiNonce': nonce,
|
||||
'apiSig': signature.toUpperCase (),
|
||||
}, params));
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if ('isAccepted' in response) {
|
||||
if (response['isAccepted']) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
};
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class foxbit extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'foxbit',
|
||||
'name': 'FoxBit',
|
||||
'countries': [ 'BR' ],
|
||||
'has': {
|
||||
'cancelOrder': true,
|
||||
'CORS': false,
|
||||
'createMarketOrder': false,
|
||||
'createOrder': true,
|
||||
'fetchBalance': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTrades': true,
|
||||
},
|
||||
'rateLimit': 1000,
|
||||
'version': 'v1',
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/51840849/87443320-01c0d080-c5fe-11ea-92e2-4ef56d32b026.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.blinktrade.com/api',
|
||||
'private': 'https://api.blinktrade.com/tapi',
|
||||
},
|
||||
'www': 'https://foxbit.com.br/exchange',
|
||||
'doc': 'https://foxbit.com.br/api/',
|
||||
},
|
||||
'comment': 'Blinktrade API',
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'{currency}/ticker', // ?crypto_currency=BTC
|
||||
'{currency}/orderbook', // ?crypto_currency=BTC
|
||||
'{currency}/trades', // ?crypto_currency=BTC&since=<TIMESTAMP>&limit=<NUMBER>
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'D', // order
|
||||
'F', // cancel order
|
||||
'U2', // balance
|
||||
'U4', // my orders
|
||||
'U6', // withdraw
|
||||
'U18', // deposit
|
||||
'U24', // confirm withdrawal
|
||||
'U26', // list withdrawals
|
||||
'U30', // list deposits
|
||||
'U34', // ledger
|
||||
'U70', // cancel withdrawal
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/VEF': { 'id': 'BTCVEF', 'symbol': 'BTC/VEF', 'base': 'BTC', 'quote': 'VEF', 'brokerId': 1, 'broker': 'SurBitcoin' },
|
||||
'BTC/VND': { 'id': 'BTCVND', 'symbol': 'BTC/VND', 'base': 'BTC', 'quote': 'VND', 'brokerId': 3, 'broker': 'VBTC' },
|
||||
'BTC/BRL': { 'id': 'BTCBRL', 'symbol': 'BTC/BRL', 'base': 'BTC', 'quote': 'BRL', 'brokerId': 4, 'broker': 'FoxBit' },
|
||||
'BTC/PKR': { 'id': 'BTCPKR', 'symbol': 'BTC/PKR', 'base': 'BTC', 'quote': 'PKR', 'brokerId': 8, 'broker': 'UrduBit' },
|
||||
'BTC/CLP': { 'id': 'BTCCLP', 'symbol': 'BTC/CLP', 'base': 'BTC', 'quote': 'CLP', 'brokerId': 9, 'broker': 'ChileBit' },
|
||||
},
|
||||
'options': {
|
||||
'brokerId': '4', // https://blinktrade.com/docs/#brokers
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'BalanceReqID': this.nonce (),
|
||||
};
|
||||
const response = await this.privatePostU2 (this.extend (request, params));
|
||||
const balances = this.safeValue (response['Responses'], this.options['brokerId']);
|
||||
const result = { 'info': response };
|
||||
if (balances !== undefined) {
|
||||
const currencyIds = Object.keys (this.currencies_by_id);
|
||||
for (let i = 0; i < currencyIds.length; i++) {
|
||||
const currencyId = currencyIds[i];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
// we only set the balance for the currency if that currency is present in response
|
||||
// otherwise we will lose the info if the currency balance has been funded or traded or not
|
||||
if (currencyId in balances) {
|
||||
const account = this.account ();
|
||||
let used = this.safeFloat (balances, currencyId + '_locked');
|
||||
if (used !== undefined) {
|
||||
used *= 1e-8;
|
||||
}
|
||||
let total = this.safeFloat (balances, currencyId);
|
||||
if (total !== undefined) {
|
||||
total *= 1e-8;
|
||||
}
|
||||
account['used'] = used;
|
||||
account['total'] = total;
|
||||
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['quote'],
|
||||
'crypto_currency': market['base'],
|
||||
};
|
||||
const response = await this.publicGetCurrencyOrderbook (this.extend (request, params));
|
||||
return this.parseOrderBook (response);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currency': market['quote'],
|
||||
'crypto_currency': market['base'],
|
||||
};
|
||||
const ticker = await this.publicGetCurrencyTicker (this.extend (request, params));
|
||||
const timestamp = this.milliseconds ();
|
||||
const lowercaseQuote = market['quote'].toLowerCase ();
|
||||
const quoteVolume = 'vol_' + lowercaseQuote;
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': this.safeFloat (ticker, 'buy'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'sell'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'vol'),
|
||||
'quoteVolume': this.safeFloat (ticker, quoteVolume),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'date');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const side = this.safeString (trade, 'side');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'order': undefined,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currency': market['quote'],
|
||||
'crypto_currency': market['base'],
|
||||
};
|
||||
const response = await this.publicGetCurrencyTrades (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
if (type === 'market') {
|
||||
throw new ExchangeError (this.id + ' allows limit orders only');
|
||||
}
|
||||
const market = this.market (symbol);
|
||||
const orderSide = (side === 'buy') ? '1' : '2';
|
||||
const request = {
|
||||
'ClOrdID': this.nonce (),
|
||||
'Symbol': market['id'],
|
||||
'Side': orderSide,
|
||||
'OrdType': '2',
|
||||
'Price': price,
|
||||
'OrderQty': amount,
|
||||
'BrokerID': market['brokerId'],
|
||||
};
|
||||
const response = await this.privatePostD (this.extend (request, params));
|
||||
const indexed = this.indexBy (response['Responses'], 'MsgType');
|
||||
const execution = indexed['8'];
|
||||
return {
|
||||
'info': response,
|
||||
'id': execution['OrderID'],
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
return await this.privatePostF (this.extend ({
|
||||
'ClOrdID': id,
|
||||
}, params));
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'public') {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
const request = this.extend ({ 'MsgType': path }, query);
|
||||
body = this.json (request);
|
||||
headers = {
|
||||
'APIKey': this.apiKey,
|
||||
'Nonce': nonce,
|
||||
'Signature': this.hmac (this.encode (nonce), this.encode (this.secret)),
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if ('Status' in response) {
|
||||
if (response['Status'] !== 200) {
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+1759
File diff suppressed because it is too large.
Load diff
Loaded 100 of 246 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user