Beta 7 with some repo names changed.
This commit is contained in:
1 parent
0f7b40cca7
commit
963c19493a
3408 files changed
+462217
-15516
No files matched your search
+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,
|
||||
}
|
||||
+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',
|
||||
}
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Usage:
|
||||
//
|
||||
// npm run export-exchanges
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require ('fs')
|
||||
, countries = require ('./countries')
|
||||
, asTable = require ('as-table')
|
||||
, 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')
|
||||
}
|
||||
|
||||
// // ----------------------------------------------------------------------------
|
||||
// // strategically placed exactly here (we can require it AFTER the export)
|
||||
// const ccxt = require ('../ccxt.js')
|
||||
// // ----------------------------------------------------------------------------
|
||||
// // create exchanges
|
||||
// const createExchange = (id) => {
|
||||
// ccxt[id].prototype.checkRequiredDependencies = () => {} // suppress it
|
||||
// return new (ccxt)[id] ()
|
||||
// }
|
||||
// const exchanges = ccxt.indexBy (ids.map (createExchange), 'id')
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TODO: REWRITE THIS ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||
|
||||
function exportSupportedAndCertifiedExchanges (exchanges, { allExchangesPaths, certifiedExchangesPaths, exchangesByCountriesPaths, proExchangesPaths }) {
|
||||
|
||||
// ............................................................................
|
||||
// markup constants and helper functions
|
||||
|
||||
const countryName = (code) => countries[code] || code
|
||||
|
||||
const ccxtCertifiedBadge = '[](https://github.com/ccxt/ccxt/wiki/Certification)'
|
||||
const ccxtProBadge = '[](https://ccxt.pro)'
|
||||
const logoHeading = ' '.repeat (7) + 'logo' + ' '.repeat (7)
|
||||
const tableHeadings = [ logoHeading, 'id', 'name', 'ver', 'doc', 'certified', 'pro' ]
|
||||
const exchangesByCountryHeading = [ 'country / region', ... tableHeadings.slice (0, 5) ]
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// list all supported exchanges
|
||||
|
||||
const exchangesNotListedInDocs = [ 'hitbtc2' ]
|
||||
|
||||
function makeTableData (exchanges) {
|
||||
return (
|
||||
values (exchanges)
|
||||
.filter (exchange => !exchangesNotListedInDocs.includes (exchange.id))
|
||||
.map (exchange => {
|
||||
let logo = exchange.urls['logo']
|
||||
let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
|
||||
let url = exchange.urls.referral || website
|
||||
let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
|
||||
let version = exchange.version ? exchange.version : '\*'
|
||||
let matches = version.match (/[^0-9]*([0-9].*)/)
|
||||
if (matches) {
|
||||
version = matches[1];
|
||||
}
|
||||
return [
|
||||
'[](' + url + ')',
|
||||
exchange.id,
|
||||
'[' + exchange.name + '](' + url + ')',
|
||||
version,
|
||||
'[API](' + doc + ')',
|
||||
exchange.certified ? ccxtCertifiedBadge : '',
|
||||
exchange.pro ? ccxtProBadge : '',
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function makeTable (jsonArray) {
|
||||
let table = asTable.configure ({ 'delimiter': ' | ' }) (jsonArray)
|
||||
let lines = table.split ("\n")
|
||||
lines.splice (1,0, lines[0].replace (/[^\|]/g, '-'))
|
||||
let headerLine = lines[1].split ('|')
|
||||
headerLine[3] = ':' + headerLine[3].slice (1, headerLine[3].length - 1) + ':'
|
||||
headerLine[4] = ':' + headerLine[4].slice (1, headerLine[4].length - 1) + ':'
|
||||
lines[1] = headerLine.join ('|')
|
||||
return lines.map (line => '|' + line + '|').join ("\n")
|
||||
}
|
||||
|
||||
if (allExchangesPaths) {
|
||||
|
||||
const tableData = makeTableData (exchanges)
|
||||
const numExchanges = tableData.length
|
||||
// prepend the table header
|
||||
tableData.splice (0, 0, tableHeadings)
|
||||
const exchangesTable = makeTable (tableData)
|
||||
const beginning = "The CCXT library currently supports the following "
|
||||
const ending = " cryptocurrency exchange markets and trading APIs:\n\n"
|
||||
const totalString = beginning + numExchanges + ending
|
||||
const allExchanges = totalString + exchangesTable + "$1"
|
||||
const allExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
|
||||
|
||||
for (const path of allExchangesPaths) {
|
||||
logExportExchanges (path, allExchangesRegex, allExchanges)
|
||||
}
|
||||
|
||||
// logExportExchanges ('README.md', allExchangesRegex, allExchanges)
|
||||
// logExportExchanges (wikiPath + '/Manual.md', allExchangesRegex, allExchanges)
|
||||
// logExportExchanges (wikiPath + '/Exchange-Markets.md', allExchangesRegex, allExchanges)
|
||||
}
|
||||
|
||||
if (proExchangesPaths) {
|
||||
const pro = values (exchanges).filter (exchange => exchange.pro)
|
||||
const tableData = makeTableData (pro)
|
||||
const numExchanges = tableData.length
|
||||
// prepend the table header
|
||||
tableData.splice (0, 0, tableHeadings)
|
||||
const exchangesTable = makeTable (tableData)
|
||||
const beginning = "The CCXT Pro library currently supports the following "
|
||||
const ending = " cryptocurrency exchange markets and WebSocket trading APIs:\n\n"
|
||||
const totalString = beginning + numExchanges + ending
|
||||
const proExchanges = totalString + exchangesTable + "$1"
|
||||
const proExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
|
||||
|
||||
for (const path of proExchangesPaths) {
|
||||
logExportExchanges (path, proExchangesRegex, proExchanges)
|
||||
}
|
||||
}
|
||||
|
||||
if (certifiedExchangesPaths) {
|
||||
const certified = values (exchanges).filter (exchange => exchange.certified)
|
||||
const tableData = makeTableData (certified)
|
||||
// prepend the table header
|
||||
tableData.splice (0, 0, tableHeadings)
|
||||
const certifiedExchangesRegex = new RegExp ("^(## Certified Cryptocurrency Exchanges\n{3})(?:\\|.+\\|$\n)+", 'm')
|
||||
const certifiedExchangesTable = makeTable (tableData)
|
||||
const certifiedExchanges = '$1' + certifiedExchangesTable + "\n"
|
||||
|
||||
for (const path of certifiedExchangesPaths) {
|
||||
logExportExchanges (path, certifiedExchangesRegex, certifiedExchanges)
|
||||
}
|
||||
|
||||
// logExportExchanges ('README.md', certifiedExchangesRegex, certifiedExchanges)
|
||||
}
|
||||
|
||||
if (exchangesByCountriesPaths) {
|
||||
let exchangesByCountries = []
|
||||
keys (countries).forEach (code => {
|
||||
let country = countries[code]
|
||||
let result = []
|
||||
keys (exchanges).forEach (id => {
|
||||
let exchange = exchanges[id]
|
||||
let logo = exchange.urls['logo']
|
||||
let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
|
||||
let url = exchange.urls.referral || website
|
||||
let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
|
||||
let version = exchange.version ? exchange.version : '\*'
|
||||
let matches = version.match (/[^0-9]*([0-9].*)/)
|
||||
if (matches)
|
||||
version = matches[1];
|
||||
let shouldInclude = false
|
||||
if (Array.isArray (exchange.countries)) {
|
||||
if (exchange.countries.indexOf (code) > -1)
|
||||
shouldInclude = true
|
||||
} else {
|
||||
if (code == exchange.countries)
|
||||
shouldInclude = true
|
||||
}
|
||||
if (shouldInclude) {
|
||||
let entry = [
|
||||
country,
|
||||
'[](' + url + ')',
|
||||
exchange.id,
|
||||
'[' + exchange.name + '](' + url + ')',
|
||||
version,
|
||||
'[API](' + doc + ')',
|
||||
// doesn't fit in width
|
||||
// exchange.certified ? ccxtCertifiedBadge : '',
|
||||
]
|
||||
result.push (entry)
|
||||
}
|
||||
})
|
||||
exchangesByCountries = exchangesByCountries.concat (result)
|
||||
});
|
||||
|
||||
const countryKeyIndex = exchangesByCountryHeading.indexOf ('country / region')
|
||||
exchangesByCountries = exchangesByCountries.sort ((a, b) => {
|
||||
const countryA = a[countryKeyIndex].toLowerCase ()
|
||||
const countryB = b[countryKeyIndex].toLowerCase ()
|
||||
if (countryA > countryB) {
|
||||
return 1
|
||||
} else if (countryA < countryB) {
|
||||
return -1;
|
||||
} else {
|
||||
if (a['id'] > b['id'])
|
||||
return 1;
|
||||
else if (a['id'] < b['id'])
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
|
||||
exchangesByCountries.splice (0, 0, exchangesByCountryHeading)
|
||||
const lines = makeTable (exchangesByCountries)
|
||||
const result = "# Exchanges By Country\n\nThe ccxt library currently supports the following cryptocurrency exchange markets and trading APIs:\n\n" + lines + "\n\n"
|
||||
for (const path of exchangesByCountriesPaths) {
|
||||
fs.truncateSync (path)
|
||||
fs.writeFileSync (path, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: REWRITE THIS ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
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,
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
"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')
|
||||
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,
|
||||
}
|
||||
+1348
File diff suppressed because it is too large.
Load diff
+87
@@ -0,0 +1,87 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Usage:
|
||||
//
|
||||
// npm run vss
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require ('fs')
|
||||
const log = require ('ololog')
|
||||
const ansi = require ('ansicolor').nice
|
||||
const { execSync } = require ('child_process')
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
|
||||
execSync ('cp ./package.json ./LICENSE.txt ./keys.json ./python/')
|
||||
|
||||
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')
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
"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.26.45'
|
||||
|
||||
Exchange.ccxtVersion = version
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const exchanges = {
|
||||
'_1btcxe': require ('./js/_1btcxe.js'),
|
||||
'acx': require ('./js/acx.js'),
|
||||
'adara': require ('./js/adara.js'),
|
||||
'anxpro': require ('./js/anxpro.js'),
|
||||
'aofex': require ('./js/aofex.js'),
|
||||
'bcex': require ('./js/bcex.js'),
|
||||
'bequant': require ('./js/bequant.js'),
|
||||
'bibox': require ('./js/bibox.js'),
|
||||
'bigone': require ('./js/bigone.js'),
|
||||
'binance': require ('./js/binance.js'),
|
||||
'binanceje': require ('./js/binanceje.js'),
|
||||
'binanceus': require ('./js/binanceus.js'),
|
||||
'bit2c': require ('./js/bit2c.js'),
|
||||
'bitbank': require ('./js/bitbank.js'),
|
||||
'bitbay': require ('./js/bitbay.js'),
|
||||
'bitfinex': require ('./js/bitfinex.js'),
|
||||
'bitfinex2': require ('./js/bitfinex2.js'),
|
||||
'bitflyer': require ('./js/bitflyer.js'),
|
||||
'bitforex': require ('./js/bitforex.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'),
|
||||
'bitso': require ('./js/bitso.js'),
|
||||
'bitstamp': require ('./js/bitstamp.js'),
|
||||
'bitstamp1': require ('./js/bitstamp1.js'),
|
||||
'bittrex': require ('./js/bittrex.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'),
|
||||
'btctradeim': require ('./js/btctradeim.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'),
|
||||
'coolcoin': require ('./js/coolcoin.js'),
|
||||
'coss': require ('./js/coss.js'),
|
||||
'crex24': require ('./js/crex24.js'),
|
||||
'deribit': require ('./js/deribit.js'),
|
||||
'digifinex': require ('./js/digifinex.js'),
|
||||
'dsx': require ('./js/dsx.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'),
|
||||
'fybse': require ('./js/fybse.js'),
|
||||
'gateio': require ('./js/gateio.js'),
|
||||
'gemini': require ('./js/gemini.js'),
|
||||
'hitbtc': require ('./js/hitbtc.js'),
|
||||
'hitbtc2': require ('./js/hitbtc2.js'),
|
||||
'hollaex': require ('./js/hollaex.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'),
|
||||
'kkex': require ('./js/kkex.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'),
|
||||
'oceanex': require ('./js/oceanex.js'),
|
||||
'okcoin': require ('./js/okcoin.js'),
|
||||
'okex': require ('./js/okex.js'),
|
||||
'paymium': require ('./js/paymium.js'),
|
||||
'poloniex': require ('./js/poloniex.js'),
|
||||
'rightbtc': require ('./js/rightbtc.js'),
|
||||
'southxchange': require ('./js/southxchange.js'),
|
||||
'stex': require ('./js/stex.js'),
|
||||
'stronghold': require ('./js/stronghold.js'),
|
||||
'surbitcoin': require ('./js/surbitcoin.js'),
|
||||
'theocean': require ('./js/theocean.js'),
|
||||
'therock': require ('./js/therock.js'),
|
||||
'tidebit': require ('./js/tidebit.js'),
|
||||
'tidex': require ('./js/tidex.js'),
|
||||
'timex': require ('./js/timex.js'),
|
||||
'topq': require ('./js/topq.js'),
|
||||
'upbit': require ('./js/upbit.js'),
|
||||
'vaultoro': require ('./js/vaultoro.js'),
|
||||
'vbtc': require ('./js/vbtc.js'),
|
||||
'whitebit': require ('./js/whitebit.js'),
|
||||
'xbtce': require ('./js/xbtce.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)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
+127685
File diff suppressed because it is too large.
Load diff
+305
@@ -0,0 +1,305 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ExchangeNotAvailable } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class _1btcxe extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': '_1btcxe',
|
||||
'name': '1BTCXE',
|
||||
'countries': [ 'PA' ], // Panama
|
||||
'comment': 'Crypto Capital API',
|
||||
'has': {
|
||||
'CORS': true,
|
||||
'withdraw': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1d': '1year',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27766049-2b294408-5ecc-11e7-85cc-adaff013dc1a.jpg',
|
||||
'api': 'https://1btcxe.com/api',
|
||||
'www': 'https://1btcxe.com',
|
||||
'doc': 'https://1btcxe.com/api-docs.php',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'stats',
|
||||
'historical-prices',
|
||||
'order-book',
|
||||
'transactions',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'balances-and-info',
|
||||
'open-orders',
|
||||
'user-transactions',
|
||||
'btc-deposit-address/get',
|
||||
'btc-deposit-address/new',
|
||||
'deposits/get',
|
||||
'withdrawals/get',
|
||||
'orders/new',
|
||||
'orders/edit',
|
||||
'orders/cancel',
|
||||
'orders/status',
|
||||
'withdrawals/new',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
return [
|
||||
{ 'id': 'USD', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'BTC', 'quoteId': 'USD' },
|
||||
{ 'id': 'EUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'BTC', 'quoteId': 'EUR' },
|
||||
{ 'id': 'CNY', 'symbol': 'BTC/CNY', 'base': 'BTC', 'quote': 'CNY', 'baseId': 'BTC', 'quoteId': 'CNY' },
|
||||
{ 'id': 'RUB', 'symbol': 'BTC/RUB', 'base': 'BTC', 'quote': 'RUB', 'baseId': 'BTC', 'quoteId': 'RUB' },
|
||||
{ 'id': 'CHF', 'symbol': 'BTC/CHF', 'base': 'BTC', 'quote': 'CHF', 'baseId': 'BTC', 'quoteId': 'CHF' },
|
||||
{ 'id': 'JPY', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'BTC', 'quoteId': 'JPY' },
|
||||
{ 'id': 'GBP', 'symbol': 'BTC/GBP', 'base': 'BTC', 'quote': 'GBP', 'baseId': 'BTC', 'quoteId': 'GBP' },
|
||||
{ 'id': 'CAD', 'symbol': 'BTC/CAD', 'base': 'BTC', 'quote': 'CAD', 'baseId': 'BTC', 'quoteId': 'CAD' },
|
||||
{ 'id': 'AUD', 'symbol': 'BTC/AUD', 'base': 'BTC', 'quote': 'AUD', 'baseId': 'BTC', 'quoteId': 'AUD' },
|
||||
{ 'id': 'AED', 'symbol': 'BTC/AED', 'base': 'BTC', 'quote': 'AED', 'baseId': 'BTC', 'quoteId': 'AED' },
|
||||
{ 'id': 'BGN', 'symbol': 'BTC/BGN', 'base': 'BTC', 'quote': 'BGN', 'baseId': 'BTC', 'quoteId': 'BGN' },
|
||||
{ 'id': 'CZK', 'symbol': 'BTC/CZK', 'base': 'BTC', 'quote': 'CZK', 'baseId': 'BTC', 'quoteId': 'CZK' },
|
||||
{ 'id': 'DKK', 'symbol': 'BTC/DKK', 'base': 'BTC', 'quote': 'DKK', 'baseId': 'BTC', 'quoteId': 'DKK' },
|
||||
{ 'id': 'HKD', 'symbol': 'BTC/HKD', 'base': 'BTC', 'quote': 'HKD', 'baseId': 'BTC', 'quoteId': 'HKD' },
|
||||
{ 'id': 'HRK', 'symbol': 'BTC/HRK', 'base': 'BTC', 'quote': 'HRK', 'baseId': 'BTC', 'quoteId': 'HRK' },
|
||||
{ 'id': 'HUF', 'symbol': 'BTC/HUF', 'base': 'BTC', 'quote': 'HUF', 'baseId': 'BTC', 'quoteId': 'HUF' },
|
||||
{ 'id': 'ILS', 'symbol': 'BTC/ILS', 'base': 'BTC', 'quote': 'ILS', 'baseId': 'BTC', 'quoteId': 'ILS' },
|
||||
{ 'id': 'INR', 'symbol': 'BTC/INR', 'base': 'BTC', 'quote': 'INR', 'baseId': 'BTC', 'quoteId': 'INR' },
|
||||
{ 'id': 'MUR', 'symbol': 'BTC/MUR', 'base': 'BTC', 'quote': 'MUR', 'baseId': 'BTC', 'quoteId': 'MUR' },
|
||||
{ 'id': 'MXN', 'symbol': 'BTC/MXN', 'base': 'BTC', 'quote': 'MXN', 'baseId': 'BTC', 'quoteId': 'MXN' },
|
||||
{ 'id': 'NOK', 'symbol': 'BTC/NOK', 'base': 'BTC', 'quote': 'NOK', 'baseId': 'BTC', 'quoteId': 'NOK' },
|
||||
{ 'id': 'NZD', 'symbol': 'BTC/NZD', 'base': 'BTC', 'quote': 'NZD', 'baseId': 'BTC', 'quoteId': 'NZD' },
|
||||
{ 'id': 'PLN', 'symbol': 'BTC/PLN', 'base': 'BTC', 'quote': 'PLN', 'baseId': 'BTC', 'quoteId': 'PLN' },
|
||||
{ 'id': 'RON', 'symbol': 'BTC/RON', 'base': 'BTC', 'quote': 'RON', 'baseId': 'BTC', 'quoteId': 'RON' },
|
||||
{ 'id': 'SEK', 'symbol': 'BTC/SEK', 'base': 'BTC', 'quote': 'SEK', 'baseId': 'BTC', 'quoteId': 'SEK' },
|
||||
{ 'id': 'SGD', 'symbol': 'BTC/SGD', 'base': 'BTC', 'quote': 'SGD', 'baseId': 'BTC', 'quoteId': 'SGD' },
|
||||
{ 'id': 'THB', 'symbol': 'BTC/THB', 'base': 'BTC', 'quote': 'THB', 'baseId': 'BTC', 'quoteId': 'THB' },
|
||||
{ 'id': 'TRY', 'symbol': 'BTC/TRY', 'base': 'BTC', 'quote': 'TRY', 'baseId': 'BTC', 'quoteId': 'TRY' },
|
||||
{ 'id': 'ZAR', 'symbol': 'BTC/ZAR', 'base': 'BTC', 'quote': 'ZAR', 'baseId': 'BTC', 'quoteId': 'ZAR' },
|
||||
];
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostBalancesAndInfo (params);
|
||||
const balance = response['balances-and-info'];
|
||||
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['available'], currencyId);
|
||||
account['used'] = this.safeFloat (balance['on_hold'], currencyId);
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'currency': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetOrderBook (this.extend (request, params));
|
||||
return this.parseOrderBook (response['order-book'], undefined, 'bid', 'ask', 'price', 'order_amount');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'currency': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetStats (this.extend (request, params));
|
||||
const ticker = this.safeValue (response, 'stats', {});
|
||||
const last = this.safeFloat (ticker, 'last_price');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': undefined,
|
||||
'datetime': undefined,
|
||||
'high': this.safeFloat (ticker, 'max'),
|
||||
'low': this.safeFloat (ticker, 'min'),
|
||||
'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': this.safeFloat (ticker, 'daily_change'),
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': undefined,
|
||||
'quoteVolume': this.safeFloat (ticker, 'total_btc_traded'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '1d', since = undefined, limit = undefined) {
|
||||
return [
|
||||
this.parse8601 (ohlcv['date'] + ' 00:00:00'),
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
this.safeFloat (ohlcv, 'price'),
|
||||
undefined,
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1d', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const response = await this.publicGetHistoricalPrices (this.extend ({
|
||||
'currency': market['id'],
|
||||
'timeframe': this.timeframes[timeframe],
|
||||
}, params));
|
||||
const ohlcvs = this.toArray (this.omit (response['historical-prices'], 'request_currency'));
|
||||
return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'timestamp');
|
||||
const id = this.safeString (trade, 'id');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const type = undefined;
|
||||
const side = this.safeString (trade, 'maker_type');
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = amount * price;
|
||||
}
|
||||
}
|
||||
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'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.publicGetTransactions (this.extend (request, params));
|
||||
const trades = this.toArray (this.omit (response['transactions'], 'request_currency'));
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'side': side,
|
||||
'type': type,
|
||||
'currency': this.marketId (symbol),
|
||||
'amount': amount,
|
||||
};
|
||||
if (type === 'limit') {
|
||||
request['limit_price'] = price;
|
||||
}
|
||||
const result = await this.privatePostOrdersNew (this.extend (request, params));
|
||||
return {
|
||||
'info': result,
|
||||
'id': result,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
return await this.privatePostOrdersCancel (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 = {
|
||||
'currency': currency['id'],
|
||||
'amount': parseFloat (amount),
|
||||
'address': address,
|
||||
};
|
||||
const response = await this.privatePostWithdrawalsNew (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['result']['uuid'],
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
if (this.id === 'cryptocapital') {
|
||||
throw new ExchangeError (this.id + ' is an abstract base API for _1btcxe');
|
||||
}
|
||||
let url = this.urls['api'] + '/' + path;
|
||||
if (api === 'public') {
|
||||
if (Object.keys (params).length) {
|
||||
url += '?' + this.urlencode (params);
|
||||
}
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const query = this.extend ({
|
||||
'api_key': this.apiKey,
|
||||
'nonce': this.nonce (),
|
||||
}, params);
|
||||
const request = this.json (query);
|
||||
query['signature'] = this.hmac (this.encode (request), this.encode (this.secret));
|
||||
body = this.json (query);
|
||||
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 (typeof response === 'string') {
|
||||
if (response.indexOf ('Maintenance') >= 0) {
|
||||
throw new ExchangeNotAvailable (this.id + ' on maintenance');
|
||||
}
|
||||
}
|
||||
if ('errors' in response) {
|
||||
let errors = [];
|
||||
for (let e = 0; e < response['errors'].length; e++) {
|
||||
const error = response['errors'][e];
|
||||
errors.push (error['code'] + ': ' + error['message']);
|
||||
}
|
||||
errors = errors.join (' ');
|
||||
throw new ExchangeError (this.id + ' ' + errors);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+473
@@ -0,0 +1,473 @@
|
||||
'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': {
|
||||
'CORS': true,
|
||||
'fetchTickers': true,
|
||||
'fetchOHLCV': true,
|
||||
'withdraw': true,
|
||||
'fetchOrder': 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 result;
|
||||
}
|
||||
|
||||
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 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, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
return [
|
||||
ohlcv[0] * 1000,
|
||||
ohlcv[1],
|
||||
ohlcv[2],
|
||||
ohlcv[3],
|
||||
ohlcv[4],
|
||||
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) {
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
} else {
|
||||
const marketId = this.safeString (order, 'market');
|
||||
symbol = this.markets_by_id[marketId]['symbol'];
|
||||
}
|
||||
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,
|
||||
'side': side,
|
||||
'price': this.safeFloat (order, 'price'),
|
||||
'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
|
||||
}
|
||||
}
|
||||
};
|
||||
+1255
File diff suppressed because it is too large.
Load diff
+1221
File diff suppressed because it is too large.
Load diff
+1125
File diff suppressed because it is too large.
Load diff
+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" },
|
||||
],
|
||||
},
|
||||
}
|
||||
+1456
File diff suppressed because it is too large.
Load diff
+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;
|
||||
+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
|
||||
)
|
||||
+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')
|
||||
))
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
'use strict'
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js')
|
||||
const { capitalize } = require ('./string')
|
||||
const { stringToBase64, utf16ToBase64, urlencodeBase64 } = require ('./encode')
|
||||
const NodeRSA = require ('./../../static_dependencies/node-rsa/NodeRSA')
|
||||
const { numberToLE } = require ('./encode')
|
||||
const EC = require ('./../../static_dependencies/elliptic/lib/elliptic').ec
|
||||
const { 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,
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
"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')
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
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 (str) {
|
||||
const arr = new Uint8Array (str.length)
|
||||
for (let i = 0; i < str.length; i++) { arr[i] = str.charCodeAt (i); }
|
||||
return CryptoJS.lib.WordArray.create (arr)
|
||||
}
|
||||
|
||||
, stringToBase64: string => CryptoJS.enc.Latin1.parse (string).toString (CryptoJS.enc.Base64)
|
||||
, utf16ToBase64: string => CryptoJS.enc.Utf16 .parse (string).toString (CryptoJS.enc.Base64)
|
||||
, base64ToBinary: string => CryptoJS.enc.Base64.parse (string)
|
||||
, base64ToString: string => CryptoJS.enc.Base64.parse (string).toString (CryptoJS.enc.Utf8)
|
||||
, binaryToBase64: binary => binary.toString (CryptoJS.enc.Base64)
|
||||
, 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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
"use strict";
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const { isObject, isNumber, isDictionary, isArray } = require ('./type')
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
const keys = Object.keys
|
||||
|
||||
, values = x => !isArray (x) // don't copy arrays if they're already arrays!
|
||||
? Object.values (x)
|
||||
: x
|
||||
|
||||
, index = x => new Set (values (x))
|
||||
|
||||
, extend = (...args) => Object.assign ({}, ...args) // NB: side-effect free
|
||||
|
||||
, clone = x => isArray (x)
|
||||
? Array.from (x) // clones arrays
|
||||
: extend (x) // clones 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
|
||||
}
|
||||
|
||||
/* ............................................. */
|
||||
|
||||
, sortBy: (array, // NB: MUTATES ARRAY!
|
||||
key,
|
||||
descending = false,
|
||||
direction = descending ? -1 : 1) => array.sort ((a, b) =>
|
||||
((a[key] < b[key]) ? -direction :
|
||||
((a[key] > b[key]) ? direction : 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 (!isObject (out)) {
|
||||
out = {}
|
||||
}
|
||||
for (const k in x) {
|
||||
out[k] = deepExtend (out[k], x[k])
|
||||
}
|
||||
} else {
|
||||
out = x
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
'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
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
'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 e = parseInt (s.split ('e-')[1])
|
||||
const neg = (s[0] === '-')
|
||||
if (e) {
|
||||
x *= Math.pow (10, e - 1)
|
||||
x = (neg ? '-' : '') + '0.' + (new Array (e)).join ('0') + x.toString ().substring (neg ? 3 : 2)
|
||||
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 (numPrecisionDigits < 0) {
|
||||
if (countingMode === TICK_SIZE) {
|
||||
throw new Error (`TICK_SIZE cant be used with negative numPrecisionDigits`)
|
||||
}
|
||||
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, 100, 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,
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
const updateCachedOrders = function (openOrders, symbol) {
|
||||
}
|
||||
|
||||
const updateOrders = function (oldOrders, newOrders, closeOpenOrders = false) {
|
||||
|
||||
for (let i = 0; i < newOrders.length; i++) {
|
||||
let newOrder = newOrders[i];
|
||||
}
|
||||
|
||||
// update local cache with open orders
|
||||
for (let j = 0; j < openOrders.length; j++) {
|
||||
const id = openOrders[j]['id'];
|
||||
this.orders[id] = openOrders[j];
|
||||
}
|
||||
let openOrdersIndexedById = this.indexBy (openOrders, 'id');
|
||||
let cachedOrderIds = Object.keys (this.orders);
|
||||
let result = [];
|
||||
for (let k = 0; k < cachedOrderIds.length; k++) {
|
||||
// match each cached order to an order in the open orders array
|
||||
// possible reasons why a cached order may be missing in the open orders array:
|
||||
// - order was closed or canceled -> update cache
|
||||
// - symbol mismatch (e.g. cached BTC/USDT, fetched ETH/USDT) -> skip
|
||||
let id = cachedOrderIds[k];
|
||||
let order = this.orders[id];
|
||||
result.push (order);
|
||||
if (!(id in openOrdersIndexedById)) {
|
||||
// cached order is not in open orders array
|
||||
// if we fetched orders by symbol and it doesn't match the cached order -> won't update the cached order
|
||||
if (symbol !== undefined && symbol !== order['symbol'])
|
||||
continue;
|
||||
// order is cached but not present in the list of open orders -> mark the cached order as closed
|
||||
if (order['status'] === 'open') {
|
||||
order = this.extend (order, {
|
||||
'status': 'closed', // likewise it might have been canceled externally (unnoticed by "us")
|
||||
'cost': undefined,
|
||||
'filled': order['amount'],
|
||||
'remaining': 0.0,
|
||||
});
|
||||
if (order['cost'] === undefined) {
|
||||
if (order['filled'] !== undefined)
|
||||
order['cost'] = order['filled'] * order['price'];
|
||||
}
|
||||
this.orders[id] = order;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
console.log (this.id, orders, openOrders, closedOrders, canceledOrders)
|
||||
process.exit ()
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
|
||||
updateOrders,
|
||||
}
|
||||
+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,
|
||||
}
|
||||
+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, '')
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+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)) })
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
'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 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 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
|
||||
, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
"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'))
|
||||
, isDictionary = o => (isObject (o) && !isArray (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)
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
'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', 'CA' ],
|
||||
'version': '1',
|
||||
'has': {
|
||||
'fetchBalance': true,
|
||||
'fetchMarkets': true,
|
||||
'createOrder': true,
|
||||
'cancelOrder': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTickers': false,
|
||||
'fetchTrades': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': true,
|
||||
'fetchClosedOrders': 'emulated',
|
||||
'fetchOpenOrders': 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'
|
||||
},
|
||||
'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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+968
@@ -0,0 +1,968 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AccountSuspended, ArgumentsRequired, AuthenticationError, DDoSProtection, ExchangeNotAvailable, InvalidOrder, OrderNotFound, PermissionDenied, InsufficientFunds } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bibox extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bibox',
|
||||
'name': 'Bibox',
|
||||
'countries': [ 'CN', 'US', 'KR' ],
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'CORS': false,
|
||||
'publicAPI': false,
|
||||
'fetchBalance': true,
|
||||
'fetchDeposits': true,
|
||||
'fetchWithdrawals': true,
|
||||
'fetchCurrencies': true,
|
||||
'fetchDepositAddress': true,
|
||||
'fetchFundingFees': true,
|
||||
'fetchTickers': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOHLCV': true,
|
||||
'createMarketOrder': false, // or they will return https://github.com/ccxt/ccxt/issues/2338
|
||||
'withdraw': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': '1min',
|
||||
'5m': '5min',
|
||||
'15m': '15min',
|
||||
'30m': '30min',
|
||||
'1h': '1hour',
|
||||
'2h': '2hour',
|
||||
'4h': '4hour',
|
||||
'6h': '6hour',
|
||||
'12h': '12hour',
|
||||
'1d': 'day',
|
||||
'1w': 'week',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/51840849/77257418-3262b000-6c85-11ea-8fb8-20bdf20b3592.jpg',
|
||||
'api': 'https://api.bibox.com',
|
||||
'www': 'https://www.bibox.com',
|
||||
'doc': [
|
||||
'https://biboxcom.github.io/en/',
|
||||
],
|
||||
'fees': 'https://bibox.zendesk.com/hc/en-us/articles/360002336133',
|
||||
'referral': 'https://w2.bibox.com/login/register?invite_code=05Kj3I',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'post': [
|
||||
// TODO: rework for full endpoint/cmd paths here
|
||||
'mdata',
|
||||
],
|
||||
'get': [
|
||||
'cquery',
|
||||
'mdata',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'cquery',
|
||||
'ctrade',
|
||||
'user',
|
||||
'orderpending',
|
||||
'transfer',
|
||||
],
|
||||
},
|
||||
'v2private': {
|
||||
'post': [
|
||||
'assets/transfer/spot',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'taker': 0.001,
|
||||
'maker': 0.0008,
|
||||
},
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': false,
|
||||
'withdraw': {},
|
||||
'deposit': {},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'2011': AccountSuspended, // Account is locked
|
||||
'2015': AuthenticationError, // Google authenticator is wrong
|
||||
'2021': InsufficientFunds, // Insufficient balance available for withdrawal
|
||||
'2027': InsufficientFunds, // Insufficient balance available (for trade)
|
||||
'2033': OrderNotFound, // operation failed! Orders have been completed or revoked
|
||||
'2067': InvalidOrder, // Does not support market orders
|
||||
'2068': InvalidOrder, // The number of orders can not be less than
|
||||
'2085': InvalidOrder, // Order quantity is too small
|
||||
'3012': AuthenticationError, // invalid apiKey
|
||||
'3024': PermissionDenied, // wrong apikey permissions
|
||||
'3025': AuthenticationError, // signature failed
|
||||
'4000': ExchangeNotAvailable, // current network is unstable
|
||||
'4003': DDoSProtection, // server busy please try again later
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'KEY': 'Bihu',
|
||||
'MTC': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025
|
||||
'PAI': 'PCHAIN',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const request = {
|
||||
'cmd': 'marketAll',
|
||||
};
|
||||
const response = await this.publicGetMdata (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "result": [
|
||||
// {
|
||||
// "is_hide":0,
|
||||
// "high_cny":"1.9478",
|
||||
// "amount":"272.41",
|
||||
// "coin_symbol":"BIX",
|
||||
// "last":"0.00002487",
|
||||
// "currency_symbol":"BTC",
|
||||
// "change":"+0.00000073",
|
||||
// "low_cny":"1.7408",
|
||||
// "base_last_cny":"1.84538041",
|
||||
// "area_id":7,
|
||||
// "percent":"+3.02%",
|
||||
// "last_cny":"1.8454",
|
||||
// "high":"0.00002625",
|
||||
// "low":"0.00002346",
|
||||
// "pair_type":0,
|
||||
// "last_usd":"0.2686",
|
||||
// "vol24H":"10940613",
|
||||
// "id":1,
|
||||
// "high_usd":"0.2835",
|
||||
// "low_usd":"0.2534"
|
||||
// }
|
||||
// ],
|
||||
// "cmd":"marketAll",
|
||||
// "ver":"1.1"
|
||||
// }
|
||||
//
|
||||
const markets = this.safeValue (response, 'result');
|
||||
const result = [];
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const numericId = this.safeInteger (market, 'id');
|
||||
const baseId = this.safeString (market, 'coin_symbol');
|
||||
const quoteId = this.safeString (market, 'currency_symbol');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const id = baseId + '_' + quoteId;
|
||||
const precision = {
|
||||
'amount': 4,
|
||||
'price': 8,
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'numericId': numericId,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': true,
|
||||
'info': market,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': Math.pow (10, -precision['amount']),
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': Math.pow (10, -precision['price']),
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
// we don't set values that are not defined by the exchange
|
||||
const timestamp = this.safeInteger (ticker, 'timestamp');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
} else {
|
||||
const baseId = this.safeString (ticker, 'coin_symbol');
|
||||
const quoteId = this.safeString (ticker, 'currency_symbol');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
const change = this.safeFloat (ticker, 'change');
|
||||
const baseVolume = this.safeFloat2 (ticker, 'vol', 'vol24H');
|
||||
let open = undefined;
|
||||
if ((last !== undefined) && (change !== undefined)) {
|
||||
open = last - change;
|
||||
}
|
||||
let percentage = this.safeString (ticker, 'percent');
|
||||
if (percentage !== undefined) {
|
||||
percentage = percentage.replace ('%', '');
|
||||
percentage = parseFloat (percentage);
|
||||
}
|
||||
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': undefined,
|
||||
'baseVolume': baseVolume,
|
||||
'quoteVolume': this.safeFloat (ticker, 'amount'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'cmd': 'ticker',
|
||||
'pair': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMdata (this.extend (request, params));
|
||||
return this.parseTicker (response['result'], market);
|
||||
}
|
||||
|
||||
parseTickers (rawTickers, symbols = undefined) {
|
||||
const tickers = [];
|
||||
for (let i = 0; i < rawTickers.length; i++) {
|
||||
const ticker = this.parseTicker (rawTickers[i]);
|
||||
if ((symbols === undefined) || (this.inArray (ticker['symbol'], symbols))) {
|
||||
tickers.push (ticker);
|
||||
}
|
||||
}
|
||||
return tickers;
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
const request = {
|
||||
'cmd': 'marketAll',
|
||||
};
|
||||
const response = await this.publicGetMdata (this.extend (request, params));
|
||||
const tickers = this.parseTickers (response['result'], symbols);
|
||||
return this.indexBy (tickers, 'symbol');
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeInteger2 (trade, 'time', 'createdAt');
|
||||
let side = this.safeInteger2 (trade, 'side', 'order_side');
|
||||
side = (side === 1) ? 'buy' : 'sell';
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
let marketId = this.safeString (trade, 'pair');
|
||||
if (marketId === undefined) {
|
||||
const baseId = this.safeString (trade, 'coin_symbol');
|
||||
const quoteId = this.safeString (trade, 'currency_symbol');
|
||||
if ((baseId !== undefined) && (quoteId !== undefined)) {
|
||||
marketId = baseId + '_' + quoteId;
|
||||
}
|
||||
}
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
let fee = undefined;
|
||||
const feeCost = this.safeFloat (trade, 'fee');
|
||||
let feeCurrency = this.safeString (trade, 'fee_symbol');
|
||||
if (feeCurrency !== undefined) {
|
||||
if (feeCurrency in this.currencies_by_id) {
|
||||
feeCurrency = this.currencies_by_id[feeCurrency]['code'];
|
||||
} else {
|
||||
feeCurrency = this.safeCurrencyCode (feeCurrency);
|
||||
}
|
||||
}
|
||||
const feeRate = undefined; // todo: deduce from market if market is defined
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined && amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrency,
|
||||
'rate': feeRate,
|
||||
};
|
||||
}
|
||||
const id = this.safeString (trade, 'id');
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'order': undefined, // Bibox does not have it (documented) yet
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'takerOrMaker': undefined,
|
||||
'side': side,
|
||||
'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 = {
|
||||
'cmd': 'deals',
|
||||
'pair': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['size'] = limit; // default = 200
|
||||
}
|
||||
const response = await this.publicGetMdata (this.extend (request, params));
|
||||
return this.parseTrades (response['result'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'cmd': 'depth',
|
||||
'pair': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['size'] = limit; // default = 200
|
||||
}
|
||||
const response = await this.publicGetMdata (this.extend (request, params));
|
||||
return this.parseOrderBook (response['result'], this.safeFloat (response['result'], 'update_time'), 'bids', 'asks', 'price', 'volume');
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
return [
|
||||
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 = 1000, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'cmd': 'kline',
|
||||
'pair': market['id'],
|
||||
'period': this.timeframes[timeframe],
|
||||
'size': limit,
|
||||
};
|
||||
const response = await this.publicGetMdata (this.extend (request, params));
|
||||
return this.parseOHLCVs (response['result'], market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async fetchCurrencies (params = {}) {
|
||||
if (!this.apiKey || !this.secret) {
|
||||
throw new AuthenticationError (this.id + " fetchCurrencies is an authenticated endpoint, therefore it requires 'apiKey' and 'secret' credentials. If you don't need currency details, set exchange.has['fetchCurrencies'] = false before calling its methods.");
|
||||
}
|
||||
const request = {
|
||||
'cmd': 'transfer/coinList',
|
||||
'body': {},
|
||||
};
|
||||
const response = await this.privatePostTransfer (this.extend (request, params));
|
||||
const currencies = this.safeValue (response, 'result');
|
||||
const result = {};
|
||||
for (let i = 0; i < currencies.length; i++) {
|
||||
const currency = currencies[i];
|
||||
const id = this.safeString (currency, 'symbol');
|
||||
const name = currency['name']; // contains hieroglyphs causing python ASCII bug
|
||||
const code = this.safeCurrencyCode (id);
|
||||
const precision = 8;
|
||||
const deposit = this.safeValue (currency, 'enable_deposit');
|
||||
const withdraw = this.safeValue (currency, 'enable_withdraw');
|
||||
const active = (deposit && withdraw);
|
||||
result[code] = {
|
||||
'id': id,
|
||||
'code': code,
|
||||
'info': currency,
|
||||
'name': name,
|
||||
'active': active,
|
||||
'fee': undefined,
|
||||
'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': Math.pow (10, precision),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const type = this.safeString (params, 'type', 'assets');
|
||||
params = this.omit (params, 'type');
|
||||
const request = {
|
||||
'cmd': 'transfer/' + type, // assets, mainAssets
|
||||
'body': this.extend ({
|
||||
'select': 1, // return full info
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostTransfer (request);
|
||||
const balances = this.safeValue (response, 'result');
|
||||
const result = { 'info': balances };
|
||||
let indexed = undefined;
|
||||
if ('assets_list' in balances) {
|
||||
indexed = this.indexBy (balances['assets_list'], 'coin_symbol');
|
||||
} else {
|
||||
indexed = balances;
|
||||
}
|
||||
const keys = Object.keys (indexed);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const id = keys[i];
|
||||
let code = id.toUpperCase ();
|
||||
if (code.indexOf ('TOTAL_') >= 0) {
|
||||
code = code.slice (6);
|
||||
}
|
||||
if (code in this.currencies_by_id) {
|
||||
code = this.currencies_by_id[code]['code'];
|
||||
}
|
||||
const account = this.account ();
|
||||
let balance = indexed[id];
|
||||
if (typeof balance === 'string') {
|
||||
balance = parseFloat (balance);
|
||||
account['free'] = balance;
|
||||
account['used'] = 0.0;
|
||||
account['total'] = balance;
|
||||
} else {
|
||||
account['free'] = this.safeFloat (balance, 'balance');
|
||||
account['used'] = this.safeFloat (balance, 'freeze');
|
||||
}
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let currency = undefined;
|
||||
const request = {
|
||||
'page': 1,
|
||||
};
|
||||
if (code !== undefined) {
|
||||
currency = this.currency (code);
|
||||
request['symbol'] = currency['id'];
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['size'] = limit;
|
||||
} else {
|
||||
request['size'] = 100;
|
||||
}
|
||||
const response = await this.privatePostTransfer ({
|
||||
'cmd': 'transfer/transferInList',
|
||||
'body': this.extend (request, params),
|
||||
});
|
||||
const deposits = this.safeValue (response['result'], 'items', []);
|
||||
for (let i = 0; i < deposits.length; i++) {
|
||||
deposits[i]['type'] = 'deposit';
|
||||
}
|
||||
return this.parseTransactions (deposits, currency, since, limit);
|
||||
}
|
||||
|
||||
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let currency = undefined;
|
||||
const request = {
|
||||
'page': 1,
|
||||
};
|
||||
if (code !== undefined) {
|
||||
currency = this.currency (code);
|
||||
request['symbol'] = currency['id'];
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['size'] = limit;
|
||||
} else {
|
||||
request['size'] = 100;
|
||||
}
|
||||
const response = await this.privatePostTransfer ({
|
||||
'cmd': 'transfer/transferOutList',
|
||||
'body': this.extend (request, params),
|
||||
});
|
||||
const withdrawals = this.safeValue (response['result'], 'items', []);
|
||||
for (let i = 0; i < withdrawals.length; i++) {
|
||||
withdrawals[i]['type'] = 'withdrawal';
|
||||
}
|
||||
return this.parseTransactions (withdrawals, currency, since, limit);
|
||||
}
|
||||
|
||||
parseTransaction (transaction, currency = undefined) {
|
||||
//
|
||||
// fetchDeposits
|
||||
//
|
||||
// {
|
||||
// 'id': 1023291,
|
||||
// 'coin_symbol': 'ETH',
|
||||
// 'to_address': '0x7263....',
|
||||
// 'amount': '0.49170000',
|
||||
// 'confirmCount': '16',
|
||||
// 'createdAt': 1553123867000,
|
||||
// 'status': 2
|
||||
// }
|
||||
//
|
||||
// fetchWithdrawals
|
||||
//
|
||||
// {
|
||||
// 'id': 521844,
|
||||
// 'coin_symbol': 'ETH',
|
||||
// 'to_address': '0xfd4e....',
|
||||
// 'addr_remark': '',
|
||||
// 'amount': '0.39452750',
|
||||
// 'fee': '0.00600000',
|
||||
// 'createdAt': 1553226906000,
|
||||
// 'memo': '',
|
||||
// 'status': 3
|
||||
// }
|
||||
//
|
||||
const id = this.safeString (transaction, 'id');
|
||||
const address = this.safeString (transaction, 'to_address');
|
||||
const currencyId = this.safeString (transaction, 'coin_symbol');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const timestamp = this.safeString (transaction, 'createdAt');
|
||||
let tag = this.safeString (transaction, 'addr_remark');
|
||||
const type = this.safeString (transaction, 'type');
|
||||
const status = this.parseTransactionStatusByType (this.safeString (transaction, 'status'), type);
|
||||
const amount = this.safeFloat (transaction, 'amount');
|
||||
let feeCost = this.safeFloat (transaction, 'fee');
|
||||
if (type === 'deposit') {
|
||||
feeCost = 0;
|
||||
tag = undefined;
|
||||
}
|
||||
const fee = {
|
||||
'cost': feeCost,
|
||||
'currency': code,
|
||||
};
|
||||
return {
|
||||
'info': transaction,
|
||||
'id': id,
|
||||
'txid': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'address': address,
|
||||
'tag': tag,
|
||||
'type': type,
|
||||
'amount': amount,
|
||||
'currency': code,
|
||||
'status': status,
|
||||
'updated': undefined,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
parseTransactionStatusByType (status, type = undefined) {
|
||||
const statuses = {
|
||||
'deposit': {
|
||||
'1': 'pending',
|
||||
'2': 'ok',
|
||||
},
|
||||
'withdrawal': {
|
||||
'0': 'pending',
|
||||
'3': 'ok',
|
||||
},
|
||||
};
|
||||
return this.safeString (this.safeValue (statuses, type, {}), status, status);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const orderType = (type === 'limit') ? 2 : 1;
|
||||
const orderSide = (side === 'buy') ? 1 : 2;
|
||||
const request = {
|
||||
'cmd': 'orderpending/trade',
|
||||
'body': this.extend ({
|
||||
'pair': market['id'],
|
||||
'account_type': 0,
|
||||
'order_type': orderType,
|
||||
'order_side': orderSide,
|
||||
'pay_bix': 0,
|
||||
'amount': amount,
|
||||
'price': price,
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostOrderpending (request);
|
||||
return {
|
||||
'info': response,
|
||||
'id': this.safeString (response, 'result'),
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
const request = {
|
||||
'cmd': 'orderpending/cancelTrade',
|
||||
'body': this.extend ({
|
||||
'orders_id': id,
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostOrderpending (request);
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'cmd': 'orderpending/order',
|
||||
'body': this.extend ({
|
||||
'id': id.toString (),
|
||||
'account_type': 0, // 0 = spot account
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostOrderpending (request);
|
||||
const order = this.safeValue (response, 'result');
|
||||
if (this.isEmpty (order)) {
|
||||
throw new OrderNotFound (this.id + ' order ' + id + ' not found');
|
||||
}
|
||||
return this.parseOrder (order);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
let marketId = undefined;
|
||||
const baseId = this.safeString (order, 'coin_symbol');
|
||||
const quoteId = this.safeString (order, 'currency_symbol');
|
||||
if ((baseId !== undefined) && (quoteId !== undefined)) {
|
||||
marketId = baseId + '_' + quoteId;
|
||||
}
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const type = (order['order_type'] === 1) ? 'market' : 'limit';
|
||||
const timestamp = order['createdAt'];
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const average = this.safeFloat (order, 'deal_price');
|
||||
const filled = this.safeFloat (order, 'deal_amount');
|
||||
const amount = this.safeFloat (order, 'amount');
|
||||
let cost = this.safeFloat2 (order, 'deal_money', 'money');
|
||||
let remaining = undefined;
|
||||
if (filled !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
remaining = amount - filled;
|
||||
}
|
||||
if (cost === undefined) {
|
||||
cost = price * filled;
|
||||
}
|
||||
}
|
||||
const side = (order['order_side'] === 1) ? 'buy' : 'sell';
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const id = this.safeString (order, 'id');
|
||||
const feeCost = this.safeFloat (order, 'fee');
|
||||
let fee = undefined;
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': undefined,
|
||||
};
|
||||
}
|
||||
cost = cost ? cost : (parseFloat (price) * filled);
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'average': average,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'fee': fee,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
// original comments from bibox:
|
||||
'1': 'open', // pending
|
||||
'2': 'open', // part completed
|
||||
'3': 'closed', // completed
|
||||
'4': 'canceled', // part canceled
|
||||
'5': 'canceled', // canceled
|
||||
'6': 'canceled', // canceling
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
let pair = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
pair = market['id'];
|
||||
}
|
||||
const size = limit ? limit : 200;
|
||||
const request = {
|
||||
'cmd': 'orderpending/orderPendingList',
|
||||
'body': this.extend ({
|
||||
'pair': pair,
|
||||
'account_type': 0, // 0 - regular, 1 - margin
|
||||
'page': 1,
|
||||
'size': size,
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostOrderpending (request);
|
||||
const orders = this.safeValue (response['result'], 'items', []);
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = 200, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a `symbol` argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'cmd': 'orderpending/pendingHistoryList',
|
||||
'body': this.extend ({
|
||||
'pair': market['id'],
|
||||
'account_type': 0, // 0 - regular, 1 - margin
|
||||
'page': 1,
|
||||
'size': limit,
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostOrderpending (request);
|
||||
const orders = this.safeValue (response['result'], 'items', []);
|
||||
return this.parseOrders (orders, 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 size = limit ? limit : 200;
|
||||
const request = {
|
||||
'cmd': 'orderpending/orderHistoryList',
|
||||
'body': this.extend ({
|
||||
'pair': market['id'],
|
||||
'account_type': 0, // 0 - regular, 1 - margin
|
||||
'page': 1,
|
||||
'size': size,
|
||||
'coin_symbol': market['baseId'],
|
||||
'currency_symbol': market['quoteId'],
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostOrderpending (request);
|
||||
const trades = this.safeValue (response['result'], 'items', []);
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'cmd': 'transfer/transferIn',
|
||||
'body': this.extend ({
|
||||
'coin_symbol': currency['id'],
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostTransfer (request);
|
||||
//
|
||||
// {
|
||||
// "result":"3Jx6RZ9TNMsAoy9NUzBwZf68QBppDruSKW","cmd":"transfer/transferIn"
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "result":"{\"account\":\"PERSONALLY OMITTED\",\"memo\":\"PERSONALLY OMITTED\"}","cmd":"transfer/transferIn"
|
||||
// }
|
||||
//
|
||||
const result = this.safeString (response, 'result');
|
||||
let address = result;
|
||||
let tag = undefined;
|
||||
if (this.isJsonEncodedObject (result)) {
|
||||
const parsed = JSON.parse (result);
|
||||
address = this.safeString (parsed, 'account');
|
||||
tag = this.safeString (parsed, 'memo');
|
||||
}
|
||||
return {
|
||||
'currency': code,
|
||||
'address': address,
|
||||
'tag': tag,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
async withdraw (code, amount, address, tag = undefined, params = {}) {
|
||||
this.checkAddress (address);
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
if (this.password === undefined) {
|
||||
if (!('trade_pwd' in params)) {
|
||||
throw new ExchangeError (this.id + ' withdraw() requires this.password set on the exchange instance or a trade_pwd parameter');
|
||||
}
|
||||
}
|
||||
if (!('totp_code' in params)) {
|
||||
throw new ExchangeError (this.id + ' withdraw() requires a totp_code parameter for 2FA authentication');
|
||||
}
|
||||
const request = {
|
||||
'trade_pwd': this.password,
|
||||
'coin_symbol': currency['id'],
|
||||
'amount': amount,
|
||||
'addr': address,
|
||||
};
|
||||
if (tag !== undefined) {
|
||||
request['address_remark'] = tag;
|
||||
}
|
||||
const response = await this.privatePostTransfer ({
|
||||
'cmd': 'transfer/transferOut',
|
||||
'body': this.extend (request, params),
|
||||
});
|
||||
return {
|
||||
'info': response,
|
||||
'id': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
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 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 = {
|
||||
'cmd': 'transfer/coinConfig',
|
||||
'body': this.extend ({
|
||||
'coin_symbol': currency['id'],
|
||||
}, params),
|
||||
};
|
||||
const response = await this.privatePostTransfer (request);
|
||||
info[code] = response;
|
||||
withdrawFees[code] = this.safeFloat (response['result'], 'withdraw_fee');
|
||||
}
|
||||
return {
|
||||
'info': info,
|
||||
'withdraw': withdrawFees,
|
||||
'deposit': {},
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.version + '/' + path;
|
||||
const cmds = this.json ([ params ]);
|
||||
if (api === 'public') {
|
||||
if (method !== 'GET') {
|
||||
body = { 'cmds': cmds };
|
||||
} else if (Object.keys (params).length) {
|
||||
url += '?' + this.urlencode (params);
|
||||
}
|
||||
} else if (api === 'v2private') {
|
||||
this.checkRequiredCredentials ();
|
||||
url = this.urls['api'] + '/v2/' + path;
|
||||
const json_params = this.json (params);
|
||||
body = {
|
||||
'body': json_params,
|
||||
'apikey': this.apiKey,
|
||||
'sign': this.hmac (this.encode (json_params), this.encode (this.secret), 'md5'),
|
||||
};
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
body = {
|
||||
'cmds': cmds,
|
||||
'apikey': this.apiKey,
|
||||
'sign': this.hmac (this.encode (cmds), this.encode (this.secret), 'md5'),
|
||||
};
|
||||
}
|
||||
if (body !== undefined) {
|
||||
body = this.json (body, { 'convertArraysToObjects': true });
|
||||
}
|
||||
headers = { '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;
|
||||
}
|
||||
if ('error' in response) {
|
||||
if ('code' in response['error']) {
|
||||
const code = this.safeString (response['error'], 'code');
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwExactlyMatchedException (this.exceptions, code, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
throw new ExchangeError (this.id + ' ' + body);
|
||||
}
|
||||
if (!('result' in response)) {
|
||||
throw new ExchangeError (this.id + ' ' + body);
|
||||
}
|
||||
}
|
||||
|
||||
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const response = await this.fetch2 (path, api, method, params, headers, body);
|
||||
if (method === 'GET') {
|
||||
return response;
|
||||
} else {
|
||||
return response['result'][0];
|
||||
}
|
||||
}
|
||||
};
|
||||
+1256
File diff suppressed because it is too large.
Load diff
+1968
File diff suppressed because it is too large.
Load diff
+53
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const binance = require ('./binance.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class binanceje extends binance {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'binanceje',
|
||||
'name': 'Binance Jersey',
|
||||
'countries': [ 'JE' ], // Jersey
|
||||
'certified': false,
|
||||
'pro': true,
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/54874009-d526eb00-4df3-11e9-928c-ce6a2b914cd1.jpg',
|
||||
'api': {
|
||||
'web': 'https://www.binance.je',
|
||||
'wapi': 'https://api.binance.je/wapi/v3',
|
||||
'public': 'https://api.binance.je/api/v1',
|
||||
'private': 'https://api.binance.je/api/v3',
|
||||
'v3': 'https://api.binance.je/api/v3',
|
||||
'v1': 'https://api.binance.je/api/v1',
|
||||
},
|
||||
'www': 'https://www.binance.je',
|
||||
'referral': 'https://www.binance.je/?ref=35047921',
|
||||
'doc': 'https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md',
|
||||
'fees': 'https://www.binance.je/fees.html',
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': false,
|
||||
'percentage': true,
|
||||
'taker': 0.0005,
|
||||
'maker': 0.0005,
|
||||
},
|
||||
// should be deleted, these are outdated and inaccurate
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': false,
|
||||
'withdraw': {
|
||||
'BTC': 0.0005,
|
||||
'ETH': 0.01,
|
||||
},
|
||||
'deposit': {},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
'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': false,
|
||||
'percentage': true,
|
||||
'taker': 0.0, // 0.1% trading fee, zero fees for all trading pairs before November 1
|
||||
'maker': 0.0, // 0.1% trading fee, zero fees for all trading pairs before November 1
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ArgumentsRequired, ExchangeError } = 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': {
|
||||
'CORS': false,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchMyTrades': 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_lasttrades',
|
||||
},
|
||||
'exceptions': {
|
||||
// { "error" : "Please provide valid APIkey" }
|
||||
// { "error" : "Please provide valid nonce in Request UInt64.TryParse failed for nonce :" }
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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'],
|
||||
};
|
||||
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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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': this.decode (signature),
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
|
||||
+567
@@ -0,0 +1,567 @@
|
||||
'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': {
|
||||
'fetchOHLCV': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchDepositAddress': 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' },
|
||||
'BTC/JPY': { 'id': 'btc_jpy', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy' },
|
||||
},
|
||||
'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));
|
||||
return this.parseTicker (response['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));
|
||||
return this.parseTrades (response['data']['transactions'], market, since, limit);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '5m', since = undefined, limit = undefined) {
|
||||
return [
|
||||
ohlcv[5],
|
||||
parseFloat (ohlcv[0]),
|
||||
parseFloat (ohlcv[1]),
|
||||
parseFloat (ohlcv[2]),
|
||||
parseFloat (ohlcv[3]),
|
||||
parseFloat (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));
|
||||
const ohlcv = this.safeValue (response['data']['candlestick'][0], '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 balances = response['data']['assets'];
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
const balance = balances[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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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 order = this.parseOrder (response['data'], market);
|
||||
const id = order['id'];
|
||||
this.orders[id] = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
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));
|
||||
return response['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));
|
||||
return this.parseOrder (response['data']);
|
||||
}
|
||||
|
||||
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));
|
||||
return this.parseOrders (response['data']['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));
|
||||
return this.parseTrades (response['data']['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));
|
||||
// Not sure about this if there could be more than one account...
|
||||
const accounts = response['data']['accounts'];
|
||||
const address = this.safeString (accounts[0], '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 txid = this.safeString (response['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;
|
||||
}
|
||||
};
|
||||
+1215
File diff suppressed because it is too large.
Load diff
+1183
File diff suppressed because it is too large.
Load diff
+1193
File diff suppressed because it is too large.
Load diff
+490
@@ -0,0 +1,490 @@
|
||||
'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...
|
||||
'has': {
|
||||
'CORS': false,
|
||||
'withdraw': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOrder': 'emulated',
|
||||
'fetchOpenOrders': 'emulated',
|
||||
'fetchClosedOrders': 'emulated',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg',
|
||||
'api': 'https://api.bitflyer.jp',
|
||||
'www': 'https://bitflyer.jp',
|
||||
'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');
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (order, 'product_code');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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 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 url = this.urls['api'] + 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 };
|
||||
}
|
||||
};
|
||||
+600
@@ -0,0 +1,600 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, OrderNotFound, InsufficientFunds, DDoSProtection } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitforex extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitforex',
|
||||
'name': 'Bitforex',
|
||||
'countries': [ 'CN' ],
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'fetchBalance': true,
|
||||
'fetchMarkets': true,
|
||||
'createOrder': true,
|
||||
'cancelOrder': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTickers': false,
|
||||
'fetchMyTrades': false,
|
||||
'fetchTrades': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': false,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchOHLCV': 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/1294454/44310033-69e9e600-a3d8-11e8-873d-54d74d1bc4e4.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': {
|
||||
'UOS': 'UOS Network',
|
||||
},
|
||||
'exceptions': {
|
||||
'4004': OrderNotFound,
|
||||
'1013': AuthenticationError,
|
||||
'1016': AuthenticationError,
|
||||
'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, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
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));
|
||||
const ohlcvs = this.safeValue (response, 'data', []);
|
||||
return this.parseOHLCVs (ohlcvs, 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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+453
@@ -0,0 +1,453 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ExchangeNotAvailable, AuthenticationError, BadRequest, PermissionDenied, InvalidAddress, ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bithumb extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bithumb',
|
||||
'name': 'Bithumb',
|
||||
'countries': [ 'KR' ], // South Korea
|
||||
'rateLimit': 500,
|
||||
'has': {
|
||||
'CORS': true,
|
||||
'fetchTickers': 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',
|
||||
'trade/place',
|
||||
'info/order_detail',
|
||||
'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,
|
||||
},
|
||||
},
|
||||
'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"}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetTickerAll (params);
|
||||
const data = this.safeValue (response, 'data');
|
||||
const currencyIds = Object.keys (data);
|
||||
const result = [];
|
||||
for (let i = 0; i < currencyIds.length; i++) {
|
||||
const currencyId = currencyIds[i];
|
||||
if (currencyId === 'date') {
|
||||
continue;
|
||||
}
|
||||
const market = data[currencyId];
|
||||
const base = currencyId;
|
||||
const quote = 'KRW';
|
||||
const 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': undefined,
|
||||
'price': undefined,
|
||||
},
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'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; // max = 50
|
||||
}
|
||||
const response = await this.publicGetOrderbookCurrency (this.extend (request, params));
|
||||
const orderbook = this.safeValue (response, 'data');
|
||||
const timestamp = this.safeInteger (orderbook, 'timestamp');
|
||||
return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'quantity');
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
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');
|
||||
let vwap = undefined;
|
||||
if (quoteVolume !== undefined && baseVolume !== undefined) {
|
||||
vwap = quoteVolume / baseVolume;
|
||||
}
|
||||
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);
|
||||
const result = {};
|
||||
const timestamp = this.safeInteger (response['data'], 'date');
|
||||
const tickers = this.omit (response['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 result;
|
||||
}
|
||||
|
||||
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));
|
||||
return this.parseTicker (response['data'], market);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
// a workaround for their bug in date format, hours are not 0-padded
|
||||
const parts = trade['transaction_date'].split (' ');
|
||||
const transaction_date = parts[0];
|
||||
let transaction_time = parts[1];
|
||||
if (transaction_time.length < 8) {
|
||||
transaction_time = '0' + transaction_time;
|
||||
}
|
||||
let timestamp = this.parse8601 (transaction_date + ' ' + transaction_time);
|
||||
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.safeFloat (trade, 'units_traded');
|
||||
let cost = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
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['base'],
|
||||
};
|
||||
if (limit === undefined) {
|
||||
request['count'] = limit; // default 20, max 100
|
||||
}
|
||||
const response = await this.publicGetTransactionHistoryCurrency (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 market = this.market (symbol);
|
||||
let request = undefined;
|
||||
let method = 'privatePostTrade';
|
||||
if (type === 'limit') {
|
||||
request = {
|
||||
'order_currency': market['id'],
|
||||
'Payment_currency': market['quote'],
|
||||
'units': amount,
|
||||
'price': price,
|
||||
'type': (side === 'buy') ? 'bid' : 'ask',
|
||||
};
|
||||
method += 'Place';
|
||||
} else if (type === 'market') {
|
||||
request = {
|
||||
'currency': market['id'],
|
||||
'units': amount,
|
||||
};
|
||||
method += 'Market' + this.capitalize (side);
|
||||
}
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
const id = this.safeString (response, 'order_id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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 (this.encode (signature)));
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Api-Key': this.apiKey,
|
||||
'Api-Sign': signature64.toString (),
|
||||
'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',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+927
@@ -0,0 +1,927 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { AuthenticationError, ArgumentsRequired, ExchangeError, InvalidOrder, BadRequest, OrderNotFound, DDoSProtection, BadSymbol } = require ('./base/errors');
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class bitmart extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'bitmart',
|
||||
'name': 'BitMart',
|
||||
'countries': [ 'US', 'CN', 'HK', 'KR' ],
|
||||
'rateLimit': 1000,
|
||||
'version': 'v2',
|
||||
'has': {
|
||||
'CORS': true,
|
||||
'fetchMarkets': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTickers': true,
|
||||
'fetchTime': true,
|
||||
'fetchCurrencies': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchTrades': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOHLCV': true,
|
||||
'fetchBalance': true,
|
||||
'createOrder': true,
|
||||
'cancelOrder': true,
|
||||
'cancelAllOrders': true,
|
||||
'fetchOrders': false,
|
||||
'fetchOrderTrades': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchCanceledOrders': true,
|
||||
'fetchOrder': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/61835713-a2662f80-ae85-11e9-9d00-6442919701fd.jpg',
|
||||
'api': 'https://openapi.bitmart.com',
|
||||
'www': 'https://www.bitmart.com/',
|
||||
'doc': 'https://github.com/bitmartexchange/bitmart-official-api-docs',
|
||||
'referral': 'http://www.bitmart.com/?r=rQCFLh',
|
||||
'fees': 'https://www.bitmart.com/fee/en',
|
||||
},
|
||||
'requiredCredentials': {
|
||||
'apiKey': true,
|
||||
'secret': true,
|
||||
'uid': true,
|
||||
},
|
||||
'api': {
|
||||
'token': {
|
||||
'post': [
|
||||
'authentication',
|
||||
],
|
||||
},
|
||||
'public': {
|
||||
'get': [
|
||||
'currencies',
|
||||
'ping',
|
||||
'steps',
|
||||
'symbols',
|
||||
'symbols_details',
|
||||
'symbols/{symbol}/kline',
|
||||
'symbols/{symbol}/orders',
|
||||
'symbols/{symbol}/trades',
|
||||
'ticker',
|
||||
'time',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'orders',
|
||||
'orders/{id}',
|
||||
'trades',
|
||||
'wallet',
|
||||
],
|
||||
'post': [
|
||||
'orders',
|
||||
],
|
||||
'delete': [
|
||||
'orders',
|
||||
'orders/{id}',
|
||||
],
|
||||
},
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': 1,
|
||||
'3m': 3,
|
||||
'5m': 5,
|
||||
'15m': 15,
|
||||
'30m': 30,
|
||||
'45m': 45,
|
||||
'1h': 60,
|
||||
'2h': 120,
|
||||
'3h': 180,
|
||||
'4h': 240,
|
||||
'1d': 1440,
|
||||
'1w': 10080,
|
||||
'1M': 43200,
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'percentage': true,
|
||||
'taker': 0.002,
|
||||
'maker': 0.001,
|
||||
'tiers': {
|
||||
'taker': [
|
||||
[0, 0.20 / 100],
|
||||
[10, 0.18 / 100],
|
||||
[50, 0.16 / 100],
|
||||
[250, 0.14 / 100],
|
||||
[1000, 0.12 / 100],
|
||||
[5000, 0.10 / 100],
|
||||
[25000, 0.08 / 100],
|
||||
[50000, 0.06 / 100],
|
||||
],
|
||||
'maker': [
|
||||
[0, 0.1 / 100],
|
||||
[10, 0.09 / 100],
|
||||
[50, 0.08 / 100],
|
||||
[250, 0.07 / 100],
|
||||
[1000, 0.06 / 100],
|
||||
[5000, 0.05 / 100],
|
||||
[25000, 0.04 / 100],
|
||||
[50000, 0.03 / 100],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {
|
||||
'Place order error': InvalidOrder, // {"message":"Place order error"}
|
||||
'Not found': OrderNotFound, // {"message":"Not found"}
|
||||
'Visit too often, please try again later': DDoSProtection, // {"code":-30,"msg":"Visit too often, please try again later","subMsg":"","data":{}}
|
||||
'Unknown symbol': BadSymbol, // {"message":"Unknown symbol"}
|
||||
},
|
||||
'broad': {
|
||||
'Invalid limit. limit must be in the range': InvalidOrder,
|
||||
'Maximum price is': InvalidOrder, // {"message":"Maximum price is 0.112695"}
|
||||
// {"message":"Required Integer parameter 'status' is not present"}
|
||||
// {"message":"Required String parameter 'symbol' is not present"}
|
||||
// {"message":"Required Integer parameter 'offset' is not present"}
|
||||
// {"message":"Required Integer parameter 'limit' is not present"}
|
||||
// {"message":"Required Long parameter 'from' is not present"}
|
||||
// {"message":"Required Long parameter 'to' is not present"}
|
||||
'is not present': BadRequest,
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'ONE': 'Menlo One',
|
||||
'PLA': 'Plair',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchTime (params = {}) {
|
||||
const response = await this.publicGetTime (params);
|
||||
//
|
||||
// {
|
||||
// "server_time": 1527777538000
|
||||
// }
|
||||
//
|
||||
return this.safeInteger (response, 'server_time');
|
||||
}
|
||||
|
||||
async signIn (params = {}) {
|
||||
const message = this.apiKey + ':' + this.secret + ':' + this.uid;
|
||||
const data = {
|
||||
'grant_type': 'client_credentials',
|
||||
'client_id': this.apiKey,
|
||||
'client_secret': this.hmac (this.encode (message), this.encode (this.secret), 'sha256'),
|
||||
};
|
||||
const response = await this.tokenPostAuthentication (this.extend (data, params));
|
||||
const accessToken = this.safeString (response, 'access_token');
|
||||
if (!accessToken) {
|
||||
throw new AuthenticationError (this.id + ' signIn() failed to authenticate. Access token missing from response.');
|
||||
}
|
||||
const expiresIn = this.safeInteger (response, 'expires_in');
|
||||
this.options['expires'] = this.sum (this.nonce (), expiresIn * 1000);
|
||||
this.options['accessToken'] = accessToken;
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const markets = await this.publicGetSymbolsDetails (params);
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "id":"1SG_BTC",
|
||||
// "base_currency":"1SG",
|
||||
// "quote_currency":"BTC",
|
||||
// "quote_increment":"0.1",
|
||||
// "base_min_size":"0.1000000000",
|
||||
// "base_max_size":"10000000.0000000000",
|
||||
// "price_min_precision":4,
|
||||
// "price_max_precision":6,
|
||||
// "expiration":"NA"
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
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 symbol = base + '/' + quote;
|
||||
//
|
||||
// https://github.com/bitmartexchange/bitmart-official-api-docs/blob/master/rest/public/symbols_details.md#response-details
|
||||
// from the above API doc:
|
||||
// quote_increment Minimum order price as well as the price increment
|
||||
// price_min_precision Minimum price precision (digit) used to query price and kline
|
||||
// price_max_precision Maximum price precision (digit) used to query price and kline
|
||||
//
|
||||
// the docs are wrong: https://github.com/ccxt/ccxt/issues/5612
|
||||
//
|
||||
const quoteIncrement = this.safeString (market, 'quote_increment');
|
||||
const amountPrecision = this.precisionFromString (quoteIncrement);
|
||||
const pricePrecision = this.safeInteger (market, 'price_max_precision');
|
||||
const precision = {
|
||||
'amount': amountPrecision,
|
||||
'price': pricePrecision,
|
||||
};
|
||||
const limits = {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'base_min_size'),
|
||||
'max': this.safeFloat (market, 'base_max_size'),
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'precision': precision,
|
||||
'limits': limits,
|
||||
'info': market,
|
||||
'active': undefined,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.milliseconds ();
|
||||
const marketId = this.safeString (ticker, 'symbol_id');
|
||||
let symbol = undefined;
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
symbol = market['symbol'];
|
||||
} else if (marketId !== undefined) {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'current_price');
|
||||
const percentage = this.safeFloat (ticker, 'fluctuation');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'highest_price'),
|
||||
'low': this.safeFloat (ticker, 'lowest_price'),
|
||||
'bid': this.safeFloat (ticker, 'bid_1'),
|
||||
'bidVolume': this.safeFloat (ticker, 'bid_1_amount'),
|
||||
'ask': this.safeFloat (ticker, 'ask_1'),
|
||||
'askVolume': this.safeFloat (ticker, 'ask_1_amount'),
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': percentage * 100,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'volume'),
|
||||
'quoteVolume': this.safeFloat (ticker, 'base_volume'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetTicker (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "volume":"97487.38",
|
||||
// "ask_1":"0.00148668",
|
||||
// "base_volume":"144.59",
|
||||
// "lowest_price":"0.00144362",
|
||||
// "bid_1":"0.00148017",
|
||||
// "highest_price":"0.00151000",
|
||||
// "ask_1_amount":"92.03",
|
||||
// "current_price":"0.00148230",
|
||||
// "fluctuation":"+0.0227",
|
||||
// "symbol_id":"XRP_ETH",
|
||||
// "url":"https://www.bitmart.com/trade?symbol=XRP_ETH",
|
||||
// "bid_1_amount":"134.78"
|
||||
// }
|
||||
//
|
||||
return this.parseTicker (response);
|
||||
}
|
||||
|
||||
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 = this.parseTicker (tickers[i]);
|
||||
const symbol = ticker['symbol'];
|
||||
result[symbol] = ticker;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchCurrencies (params = {}) {
|
||||
const currencies = await this.publicGetCurrencies (params);
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "name":"CNY1",
|
||||
// "withdraw_enabled":false,
|
||||
// "id":"CNY1",
|
||||
// "deposit_enabled":false
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
const result = {};
|
||||
for (let i = 0; i < currencies.length; i++) {
|
||||
const currency = currencies[i];
|
||||
const currencyId = this.safeString (currency, 'id');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const name = this.safeString (currency, 'name');
|
||||
const withdrawEnabled = this.safeValue (currency, 'withdraw_enabled');
|
||||
const depositEnabled = this.safeValue (currency, 'deposit_enabled');
|
||||
const active = withdrawEnabled && depositEnabled;
|
||||
result[code] = {
|
||||
'id': currencyId,
|
||||
'code': code,
|
||||
'name': name,
|
||||
'info': currency, // the original payload
|
||||
'active': active,
|
||||
'fee': undefined,
|
||||
'precision': undefined,
|
||||
'limits': {
|
||||
'amount': { 'min': undefined, 'max': undefined },
|
||||
'price': { 'min': undefined, 'max': undefined },
|
||||
'cost': { 'min': undefined, 'max': undefined },
|
||||
'withdraw': { 'min': undefined, 'max': undefined },
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
// 'precision': 4, // optional price precision / depth level whose range is defined in symbol details
|
||||
};
|
||||
const response = await this.publicGetSymbolsSymbolOrders (this.extend (request, params));
|
||||
return this.parseOrderBook (response, undefined, 'buys', 'sells', 'price', 'amount');
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
//
|
||||
// fetchTrades (public)
|
||||
//
|
||||
// {
|
||||
// "amount":"2.29275119",
|
||||
// "price":"0.021858",
|
||||
// "count":"104.8930",
|
||||
// "order_time":1563997286061,
|
||||
// "type":"sell"
|
||||
// }
|
||||
//
|
||||
// fetchMyTrades (private)
|
||||
//
|
||||
// {
|
||||
// active: true,
|
||||
// amount: '0.2000',
|
||||
// entrustType: 1,
|
||||
// entrust_id: 979648824,
|
||||
// fees: '0.0000085532',
|
||||
// price: '0.021383',
|
||||
// symbol: 'ETH_BTC',
|
||||
// timestamp: 1574343514000,
|
||||
// trade_id: 329418828
|
||||
// },
|
||||
//
|
||||
const id = this.safeString (trade, 'trade_id');
|
||||
const timestamp = this.safeInteger2 (trade, 'timestamp', 'order_time');
|
||||
const type = undefined;
|
||||
let side = this.safeStringLower (trade, 'type');
|
||||
if ((side === undefined) && ('entrustType' in trade)) {
|
||||
side = trade['entrustType'] ? 'sell' : 'buy';
|
||||
}
|
||||
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 orderId = this.safeInteger (trade, 'entrust_id');
|
||||
const marketId = this.safeString (trade, 'symbol');
|
||||
let symbol = undefined;
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
symbol = market['symbol'];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if (symbol === undefined) {
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
}
|
||||
const feeCost = this.safeFloat (trade, 'fees');
|
||||
let fee = undefined;
|
||||
if (feeCost !== undefined) {
|
||||
let feeCurrencyCode = undefined;
|
||||
if (market !== undefined) {
|
||||
feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote'];
|
||||
}
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrencyCode,
|
||||
};
|
||||
}
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'order': orderId,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'takerOrMaker': undefined,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
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.publicGetSymbolsSymbolTrades (this.extend (request, params));
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "amount":"2.29275119",
|
||||
// "price":"0.021858",
|
||||
// "count":"104.8930",
|
||||
// "order_time":1563997286061,
|
||||
// "type":"sell"
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
return this.parseTrades (response, 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);
|
||||
// limit is required, must be in the range (0, 50)
|
||||
const maxLimit = 50;
|
||||
limit = (limit === undefined) ? maxLimit : Math.min (limit, maxLimit);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'offset': 0, // current page, starts from 0
|
||||
'limit': limit, // required
|
||||
};
|
||||
const response = await this.privateGetTrades (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "total_trades": 216,
|
||||
// "total_pages": 22,
|
||||
// "current_page": 0,
|
||||
// "trades": [
|
||||
// {
|
||||
// "symbol": "BMX_ETH",
|
||||
// "amount": "1.0",
|
||||
// "fees": "0.0005000000",
|
||||
// "trade_id": 2734956,
|
||||
// "price": "0.00013737",
|
||||
// "active": true,
|
||||
// "entrust_id": 5576623,
|
||||
// "timestamp": 1545292334000
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const trades = this.safeValue (response, 'trades', []);
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'entrust_id': id,
|
||||
};
|
||||
return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params));
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
return [
|
||||
this.safeInteger (ohlcv, 'timestamp'),
|
||||
this.safeFloat (ohlcv, 'open_price'),
|
||||
this.safeFloat (ohlcv, 'highest_price'),
|
||||
this.safeFloat (ohlcv, 'lowest_price'),
|
||||
this.safeFloat (ohlcv, 'current_price'),
|
||||
this.safeFloat (ohlcv, 'volume'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
if (since === undefined && limit === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOHLCV requires either a `since` argument or a `limit` argument (or both)');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const periodInSeconds = this.parseTimeframe (timeframe);
|
||||
const duration = periodInSeconds * limit * 1000;
|
||||
let to = this.milliseconds ();
|
||||
if (since === undefined) {
|
||||
since = to - duration;
|
||||
} else {
|
||||
to = this.sum (since, duration);
|
||||
}
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'from': since, // start time of k-line data (in milliseconds, required)
|
||||
'to': to, // end time of k-line data (in milliseconds, required)
|
||||
'step': this.timeframes[timeframe], // steps of sampling (in minutes, default 1 minute, optional)
|
||||
};
|
||||
const response = await this.publicGetSymbolsSymbolKline (this.extend (request, params));
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "timestamp":1525761000000,
|
||||
// "open_price":"0.010130",
|
||||
// "highest_price":"0.010130",
|
||||
// "lowest_price":"0.010130",
|
||||
// "current_price":"0.010130",
|
||||
// "volume":"0.000000"
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
return this.parseOHLCVs (response, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const balances = await this.privateGetWallet (params);
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "name":"Bitcoin",
|
||||
// "available":"0.0000000000",
|
||||
// "frozen":"0.0000000000",
|
||||
// "id":"BTC"
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
const result = { 'info': 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'] = this.safeFloat (balance, 'available');
|
||||
account['used'] = this.safeFloat (balance, 'frozen');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// createOrder
|
||||
//
|
||||
// {
|
||||
// "entrust_id":1223181
|
||||
// }
|
||||
//
|
||||
// cancelOrder
|
||||
//
|
||||
// {}
|
||||
//
|
||||
// fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders
|
||||
//
|
||||
// {
|
||||
// "entrust_id":1223181,
|
||||
// "symbol":"BMX_ETH",
|
||||
// "timestamp":1528060666000,
|
||||
// "side":"buy",
|
||||
// "price":"1.000000",
|
||||
// "fees":"0.1",
|
||||
// "original_amount":"1",
|
||||
// "executed_amount":"1",
|
||||
// "remaining_amount":"0",
|
||||
// "status":3
|
||||
// }
|
||||
//
|
||||
const id = this.safeString (order, 'entrust_id');
|
||||
const timestamp = this.milliseconds ();
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (order, 'symbol');
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if ((symbol === undefined) && (market !== undefined)) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const amount = this.safeFloat (order, 'original_amount');
|
||||
let cost = undefined;
|
||||
let filled = this.safeFloat (order, 'executed_amount');
|
||||
let remaining = this.safeFloat (order, 'remaining_amount');
|
||||
if (amount !== undefined) {
|
||||
if (remaining !== undefined) {
|
||||
if (filled === undefined) {
|
||||
filled = amount - remaining;
|
||||
}
|
||||
}
|
||||
if (filled !== undefined) {
|
||||
if (remaining === undefined) {
|
||||
remaining = amount - filled;
|
||||
}
|
||||
if (cost === undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * filled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const side = this.safeString (order, 'side');
|
||||
const type = undefined;
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'info': order,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': undefined,
|
||||
'average': undefined,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'fee': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'0': 'all',
|
||||
'1': 'open',
|
||||
'2': 'open',
|
||||
'3': 'closed',
|
||||
'4': 'canceled',
|
||||
'5': 'open',
|
||||
'6': 'closed',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
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 market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'side': side.toLowerCase (),
|
||||
'amount': this.amountToPrecision (symbol, amount),
|
||||
'price': this.priceToPrecision (symbol, price),
|
||||
};
|
||||
const response = await this.privatePostOrders (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "entrust_id":1223181
|
||||
// }
|
||||
//
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const intId = parseInt (id);
|
||||
const request = {
|
||||
'id': intId,
|
||||
'entrust_id': intId,
|
||||
};
|
||||
const response = await this.privateDeleteOrdersId (this.extend (request, params));
|
||||
//
|
||||
// responds with an empty object {}
|
||||
//
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
async cancelAllOrders (symbol = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument');
|
||||
}
|
||||
const side = this.safeString (params, 'side');
|
||||
if (side === undefined) {
|
||||
throw new ArgumentsRequired (this.id + " cancelAllOrders requires a `side` parameter ('buy' or 'sell')");
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'side': side, // 'buy' or 'sell'
|
||||
};
|
||||
const response = await this.privateDeleteOrders (this.extend (request, params));
|
||||
//
|
||||
// responds with an empty object {}
|
||||
//
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOrdersByStatus requires a symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
if (limit === undefined) {
|
||||
limit = 500; // default 500, max 1000
|
||||
}
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'status': status,
|
||||
'offset': 0, // current page, starts from 0
|
||||
'limit': limit,
|
||||
};
|
||||
const response = await this.privateGetOrders (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "orders":[
|
||||
// {
|
||||
// "entrust_id":1223181,
|
||||
// "symbol":"BMX_ETH",
|
||||
// "timestamp":1528060666000,
|
||||
// "side":"buy",
|
||||
// "price":"1.000000",
|
||||
// "fees":"0.1",
|
||||
// "original_amount":"1",
|
||||
// "executed_amount":"1",
|
||||
// "remaining_amount":"0",
|
||||
// "status":3
|
||||
// }
|
||||
// ],
|
||||
// "total_pages":1,
|
||||
// "total_orders":1,
|
||||
// "current_page":0,
|
||||
// }
|
||||
//
|
||||
const orders = this.safeValue (response, 'orders', []);
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
// 5 = pending & partially filled orders
|
||||
return await this.fetchOrdersByStatus (5, symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
// 3 = closed orders
|
||||
return await this.fetchOrdersByStatus (3, symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchCanceledOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
// 4 = canceled orders
|
||||
return await this.fetchOrdersByStatus (4, symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privateGetOrdersId (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "entrust_id":1223181,
|
||||
// "symbol":"BMX_ETH",
|
||||
// "timestamp":1528060666000,
|
||||
// "side":"buy",
|
||||
// "price":"1.000000",
|
||||
// "fees":"0.1",
|
||||
// "original_amount":"1",
|
||||
// "executed_amount":"1",
|
||||
// "remaining_amount":"0",
|
||||
// "status":3
|
||||
// }
|
||||
//
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + this.version + '/' + 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 if (api === 'token') {
|
||||
this.checkRequiredCredentials ();
|
||||
body = this.urlencode (query);
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
} else {
|
||||
const nonce = this.nonce ();
|
||||
this.checkRequiredCredentials ();
|
||||
const token = this.safeString (this.options, 'accessToken');
|
||||
if (token === undefined) {
|
||||
throw new AuthenticationError (this.id + ' ' + path + ' endpoint requires an accessToken option or a prior call to signIn() method');
|
||||
}
|
||||
const expires = this.safeInteger (this.options, 'expires');
|
||||
if (expires !== undefined) {
|
||||
if (nonce >= expires) {
|
||||
throw new AuthenticationError (this.id + ' accessToken expired, supply a new accessToken or call the signIn() method');
|
||||
}
|
||||
}
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-BM-TIMESTAMP': nonce.toString (),
|
||||
'X-BM-AUTHORIZATION': 'Bearer ' + token,
|
||||
};
|
||||
if (method !== 'GET') {
|
||||
query = this.keysort (query);
|
||||
body = this.json (query);
|
||||
const message = this.urlencode (query);
|
||||
headers['X-BM-SIGNATURE'] = this.hmac (this.encode (message), this.encode (this.secret), 'sha256');
|
||||
}
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
// {"message":"Maximum price is 0.112695"}
|
||||
// {"message":"Required Integer parameter 'status' is not present"}
|
||||
// {"message":"Required String parameter 'symbol' is not present"}
|
||||
// {"message":"Required Integer parameter 'offset' is not present"}
|
||||
// {"message":"Required Integer parameter 'limit' is not present"}
|
||||
// {"message":"Required Long parameter 'from' is not present"}
|
||||
// {"message":"Required Long parameter 'to' is not present"}
|
||||
// {"message":"Invalid status. status=6 not support any more, please use 3:deal_success orders, 4:cancelled orders"}
|
||||
// {"message":"Not found"}
|
||||
// {"message":"Place order error"}
|
||||
//
|
||||
const feedback = this.id + ' ' + body;
|
||||
const message = this.safeString2 (response, 'message', 'msg');
|
||||
if (message !== undefined) {
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback);
|
||||
this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback);
|
||||
throw new ExchangeError (feedback); // unknown message
|
||||
}
|
||||
}
|
||||
};
|
||||
+1188
File diff suppressed because it is too large.
Load diff
+1329
File diff suppressed because it is too large.
Load diff
+567
@@ -0,0 +1,567 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, InvalidNonce, AuthenticationError, OrderNotFound } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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': {
|
||||
'CORS': false,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchOrder': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27766335-715ce7aa-5ed5-11e7-88a8-173a27bb30fe.jpg',
|
||||
'api': 'https://api.bitso.com',
|
||||
'www': 'https://bitso.com',
|
||||
'doc': 'https://bitso.com/api_info',
|
||||
'fees': 'https://bitso.com/fees?l=es',
|
||||
'referral': 'https://bitso.com/?ref=itej',
|
||||
},
|
||||
'options': {
|
||||
'precision': {
|
||||
'XRP': 6,
|
||||
'MXN': 2,
|
||||
'TUSD': 2,
|
||||
},
|
||||
'defaultPrecision': 8,
|
||||
},
|
||||
'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);
|
||||
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 precision = {
|
||||
'amount': this.safeInteger (this.options['precision'], base, this.options['defaultPrecision']),
|
||||
'price': this.safeInteger (this.options['precision'], quote, this.options['defaultPrecision']),
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'info': market,
|
||||
'limits': limits,
|
||||
'precision': precision,
|
||||
'active': undefined,
|
||||
});
|
||||
}
|
||||
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'));
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (trade, 'book');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const 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'));
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (order, 'book');
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if (symbol === undefined) {
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
}
|
||||
const 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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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 (code === 'XRP') {
|
||||
const parts = address.split ('?dt=');
|
||||
address = parts[0];
|
||||
tag = 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));
|
||||
}
|
||||
};
|
||||
+1196
File diff suppressed because it is too large.
Load diff
+291
@@ -0,0 +1,291 @@
|
||||
'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': {
|
||||
'CORS': 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.0025, 'taker': 0.0025 },
|
||||
'BTC/EUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'EUR/USD': { 'id': 'eurusd', 'symbol': 'EUR/USD', 'base': 'EUR', 'quote': 'USD', 'baseId': 'eur', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'XRP/USD': { 'id': 'xrpusd', 'symbol': 'XRP/USD', 'base': 'XRP', 'quote': 'USD', 'baseId': 'xrp', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'XRP/EUR': { 'id': 'xrpeur', 'symbol': 'XRP/EUR', 'base': 'XRP', 'quote': 'EUR', 'baseId': 'xrp', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'LTC/USD': { 'id': 'ltcusd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'ltc', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'LTC/EUR': { 'id': 'ltceur', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'ltc', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'ETH/USD': { 'id': 'ethusd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'eth', 'quoteId': 'usd', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'ETH/EUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'eth', 'quoteId': 'eur', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.0025, 'taker': 0.0025 },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
+1540
File diff suppressed because it is too large.
Load diff
+1214
File diff suppressed because it is too large.
Load diff
+233
@@ -0,0 +1,233 @@
|
||||
'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,
|
||||
},
|
||||
'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': this.decode (signature),
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+839
@@ -0,0 +1,839 @@
|
||||
'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': {
|
||||
'CORS': true,
|
||||
'cancelOrder': false, // todo
|
||||
'createLimitOrder': false, // todo
|
||||
'createMarketOrder': false, // todo
|
||||
'createOrder': false, // todo
|
||||
'editOrder': false, // todo
|
||||
'withdraw': false, // todo
|
||||
'fetchTrades': false,
|
||||
'fetchTickers': true,
|
||||
'fetchTicker': true,
|
||||
'fetchOrders': false,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchWithdrawals': true,
|
||||
'fetchOrderTrades': false,
|
||||
'fetchLedger': true,
|
||||
'fetchDepositAddress': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'15m': '15m',
|
||||
'20m': '20m',
|
||||
'30m': '30m',
|
||||
'1h': '1h',
|
||||
'2h': '2h',
|
||||
'3h': '3h',
|
||||
'4h': '4h',
|
||||
'6h': '6h',
|
||||
'8h': '8h',
|
||||
'12h': '12h',
|
||||
'1d': '1d',
|
||||
},
|
||||
'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,
|
||||
},
|
||||
'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,
|
||||
'symbolSeparator': '_',
|
||||
},
|
||||
});
|
||||
// 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 = response['result'][0];
|
||||
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'));
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (ticker, 'MarketName');
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
} else {
|
||||
symbol = this.parseSymbol (marketId);
|
||||
}
|
||||
}
|
||||
if ((symbol === undefined) && (market !== undefined)) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const 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, timeframe = '1d', since = undefined, limit = undefined) {
|
||||
const timestamp = this.parse8601 (ohlcv['TimeStamp'] + '+00:00');
|
||||
return [
|
||||
timestamp,
|
||||
this.safeFloat (ohlcv, 'Open'),
|
||||
this.safeFloat (ohlcv, 'High'),
|
||||
this.safeFloat (ohlcv, 'Low'),
|
||||
this.safeFloat (ohlcv, 'Close'),
|
||||
this.safeFloat (ohlcv, 'Volume'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '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));
|
||||
return this.parseOHLCVs (response['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);
|
||||
}
|
||||
|
||||
parseSymbol (id) {
|
||||
let [ base, quote ] = id.split (this.options['symbolSeparator']);
|
||||
base = this.safeCurrencyCode (base);
|
||||
quote = this.safeCurrencyCode (quote);
|
||||
return base + '/' + quote;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.v3PrivatePostGetbalances (params);
|
||||
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'));
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (order, 'Exchange');
|
||||
if (marketId === undefined) {
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
} else {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
symbol = market['symbol'];
|
||||
} else {
|
||||
symbol = this.parseSymbol (marketId);
|
||||
}
|
||||
}
|
||||
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',
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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"}
|
||||
//
|
||||
if (body[0] === '{') {
|
||||
const success = this.safeValue (response, 'success');
|
||||
if (success === undefined) {
|
||||
throw new ExchangeError (this.id + ': malformed response: ' + this.json (response));
|
||||
}
|
||||
if (!success) {
|
||||
const feedback = this.id + ' ' + body;
|
||||
const errorCode = this.safeString (response, 'result');
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+618
@@ -0,0 +1,618 @@
|
||||
'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': {
|
||||
'fetchCurrencies': true,
|
||||
'fetchTickers': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchDepositAddress': true,
|
||||
'fetchOrder': 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.markets_by_id[marketId];
|
||||
const symbol = market['symbol'];
|
||||
result[symbol] = this.parseTicker (response[marketId], market);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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"
|
||||
// }
|
||||
//
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (order, 'market');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
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';
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'side': order['type'],
|
||||
'price': price,
|
||||
'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));
|
||||
const success = this.safeInteger (response, 'success');
|
||||
if (success !== 1) {
|
||||
throw new InvalidOrder (this.id + ' ' + this.json (response));
|
||||
}
|
||||
let parts = response['message'].split (' / ');
|
||||
parts = parts.slice (1);
|
||||
const feeParts = parts[5].split (' ');
|
||||
const order = this.parseOrder ({
|
||||
'timestamp': this.milliseconds (),
|
||||
'order_number': response['order_number'],
|
||||
'type': parts[0].toLowerCase (),
|
||||
'market': parts[0].toLowerCase (),
|
||||
'amount': parts[2].split (' ')[1],
|
||||
'price': parts[3].split (' ')[1],
|
||||
'total': parts[4].split (' ')[1],
|
||||
'fee': {
|
||||
'cost': parseFloat (feeParts[1]),
|
||||
'currency': feeParts[2],
|
||||
},
|
||||
'progress': '0.0',
|
||||
'info': response,
|
||||
}, market);
|
||||
const id = order['id'];
|
||||
this.orders[id] = order;
|
||||
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));
|
||||
return this.parseOrders (response['order_open'], 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));
|
||||
return this.parseTrades (response['trade_history'], 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;
|
||||
}
|
||||
};
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
'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': {
|
||||
'fetchTicker': false,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchMyTrades': 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, timeframe = '5m', since = undefined, limit = undefined) {
|
||||
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));
|
||||
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',
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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);
|
||||
}
|
||||
};
|
||||
+417
@@ -0,0 +1,417 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchTickers': false,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/31275803-4df755a8-aaa1-11e7-9abb-11ec2fad9f2d.jpg',
|
||||
'api': 'https://www.btcbox.co.jp/api',
|
||||
'www': 'https://www.btcbox.co.jp/',
|
||||
'doc': 'https://www.btcbox.co.jp/help/asm',
|
||||
'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,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'price': price,
|
||||
'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;
|
||||
}
|
||||
};
|
||||
+758
@@ -0,0 +1,758 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, OrderNotFound, ArgumentsRequired, InvalidOrder, DDoSProtection } = require ('./base/errors');
|
||||
const { ROUND } = require ('./base/functions/number');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class btcmarkets extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'btcmarkets',
|
||||
'name': 'BTC Markets',
|
||||
'countries': [ 'AU' ], // Australia
|
||||
'rateLimit': 1000, // market data cached for 1 second (trades cached for 2 seconds)
|
||||
'has': {
|
||||
'CORS': false,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': true,
|
||||
'fetchClosedOrders': 'emulated',
|
||||
'fetchOpenOrders': true,
|
||||
'fetchMyTrades': true,
|
||||
'cancelOrders': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/29142911-0e1acfc2-7d5c-11e7-98c4-07d9532b29d7.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.btcmarkets.net',
|
||||
'private': 'https://api.btcmarkets.net',
|
||||
'web': 'https://btcmarkets.net/data',
|
||||
},
|
||||
'www': 'https://btcmarkets.net',
|
||||
'doc': 'https://github.com/BTCMarkets/API',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'market/{id}/tick',
|
||||
'market/{id}/orderbook',
|
||||
'market/{id}/trades',
|
||||
'v2/market/{id}/tickByTime/{timeframe}',
|
||||
'v2/market/{id}/trades',
|
||||
'v2/market/active',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'account/balance',
|
||||
'account/{id}/tradingfee',
|
||||
'fundtransfer/history',
|
||||
'v2/order/open',
|
||||
'v2/order/open/{id}',
|
||||
'v2/order/history/{instrument}/{currency}/',
|
||||
'v2/order/trade/history/{id}',
|
||||
'v2/transaction/history/{currency}',
|
||||
],
|
||||
'post': [
|
||||
'fundtransfer/withdrawCrypto',
|
||||
'fundtransfer/withdrawEFT',
|
||||
'order/create',
|
||||
'order/cancel',
|
||||
'order/history',
|
||||
'order/open',
|
||||
'order/trade/history',
|
||||
'order/createBatch', // they promise it's coming soon...
|
||||
'order/detail',
|
||||
],
|
||||
},
|
||||
'web': {
|
||||
'get': [
|
||||
'market/BTCMarkets/{id}/tickByTime',
|
||||
],
|
||||
},
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': 'minute',
|
||||
'1h': 'hour',
|
||||
'1d': 'day',
|
||||
},
|
||||
'exceptions': {
|
||||
'3': InvalidOrder,
|
||||
'6': DDoSProtection,
|
||||
},
|
||||
'fees': {
|
||||
'percentage': true,
|
||||
'tierBased': true,
|
||||
'maker': -0.05 / 100,
|
||||
'taker': 0.20 / 100,
|
||||
},
|
||||
'options': {
|
||||
'fees': {
|
||||
'AUD': {
|
||||
'maker': 0.85 / 100,
|
||||
'taker': 0.85 / 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['since'] = since;
|
||||
}
|
||||
const response = await this.privateGetFundtransferHistory (this.extend (request, params));
|
||||
const transactions = response['fundTransfers'];
|
||||
return this.parseTransactions (transactions, undefined, since, limit);
|
||||
}
|
||||
|
||||
parseTransactionStatus (status) {
|
||||
// todo: find more statuses
|
||||
const statuses = {
|
||||
'Complete': 'ok',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseTransaction (item, currency = undefined) {
|
||||
//
|
||||
// {
|
||||
// status: 'Complete',
|
||||
// fundTransferId: 1904311906,
|
||||
// description: 'ETH withdraw from [me@email.com] to Address: 0xF123aa44FadEa913a7da99cc2eE202Db684Ce0e3 amount: 8.28965701 fee: 0.00000000',
|
||||
// creationTime: 1529418358525,
|
||||
// currency: 'ETH',
|
||||
// amount: 828965701,
|
||||
// fee: 0,
|
||||
// transferType: 'WITHDRAW',
|
||||
// errorMessage: null,
|
||||
// lastUpdate: 1529418376754,
|
||||
// cryptoPaymentDetail: {
|
||||
// address: '0xF123aa44FadEa913a7da99cc2eE202Db684Ce0e3',
|
||||
// txId: '0x8fe483b6f9523559b9ebffb29624f98e86227d2660d4a1fd4785d45e51c662c2'
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// status: 'Complete',
|
||||
// fundTransferId: 494077500,
|
||||
// description: 'BITCOIN Deposit, B 0.1000',
|
||||
// creationTime: 1501077601015,
|
||||
// currency: 'BTC',
|
||||
// amount: 10000000,
|
||||
// fee: 0,
|
||||
// transferType: 'DEPOSIT',
|
||||
// errorMessage: null,
|
||||
// lastUpdate: 1501077601133,
|
||||
// cryptoPaymentDetail: null
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "fee": 0,
|
||||
// "amount": 56,
|
||||
// "status": "Complete",
|
||||
// "currency": "BCHABC",
|
||||
// "lastUpdate": 1542339164044,
|
||||
// "description": "BitcoinCashABC Deposit, P 0.00000056",
|
||||
// "creationTime": 1542339164003,
|
||||
// "errorMessage": null,
|
||||
// "transferType": "DEPOSIT",
|
||||
// "fundTransferId": 2527326972,
|
||||
// "cryptoPaymentDetail": null
|
||||
// }
|
||||
//
|
||||
const timestamp = this.safeInteger (item, 'creationTime');
|
||||
const lastUpdate = this.safeInteger (item, 'lastUpdate');
|
||||
const transferType = this.safeString (item, 'transferType');
|
||||
const cryptoPaymentDetail = this.safeValue (item, 'cryptoPaymentDetail', {});
|
||||
const address = this.safeString (cryptoPaymentDetail, 'address');
|
||||
const txid = this.safeString (cryptoPaymentDetail, 'txId');
|
||||
let type = undefined;
|
||||
if (transferType === 'DEPOSIT') {
|
||||
type = 'deposit';
|
||||
} else if (transferType === 'WITHDRAW') {
|
||||
type = 'withdrawal';
|
||||
} else {
|
||||
type = transferType;
|
||||
}
|
||||
const fee = this.safeFloat (item, 'fee');
|
||||
const status = this.parseTransactionStatus (this.safeString (item, 'status'));
|
||||
const ccy = this.safeString (item, 'currency');
|
||||
const code = this.safeCurrencyCode (ccy);
|
||||
// todo: this logic is duplicated below
|
||||
let amount = this.safeFloat (item, 'amount');
|
||||
if (amount !== undefined) {
|
||||
amount = amount * 1e-8;
|
||||
}
|
||||
return {
|
||||
'id': this.safeString (item, 'fundTransferId'),
|
||||
'txid': txid,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'address': address,
|
||||
'tag': undefined,
|
||||
'type': type,
|
||||
'amount': amount,
|
||||
'currency': code,
|
||||
'status': status,
|
||||
'updated': lastUpdate,
|
||||
'fee': {
|
||||
'currency': code,
|
||||
'cost': fee,
|
||||
},
|
||||
'info': item,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetV2MarketActive (params);
|
||||
const result = [];
|
||||
const markets = this.safeValue (response, 'markets');
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const baseId = this.safeString (market, 'instrument');
|
||||
const quoteId = this.safeString (market, 'currency');
|
||||
const id = baseId + '/' + quoteId;
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const fees = this.safeValue (this.safeValue (this.options, 'fees', {}), quote, this.fees);
|
||||
let pricePrecision = 2;
|
||||
let amountPrecision = 4;
|
||||
const minAmount = 0.001; // where does it come from?
|
||||
let minPrice = undefined;
|
||||
if (quote === 'AUD') {
|
||||
if ((base === 'XRP') || (base === 'OMG')) {
|
||||
pricePrecision = 4;
|
||||
}
|
||||
amountPrecision = -Math.log10 (minAmount);
|
||||
minPrice = Math.pow (10, -pricePrecision);
|
||||
}
|
||||
const precision = {
|
||||
'amount': amountPrecision,
|
||||
'price': pricePrecision,
|
||||
};
|
||||
const limits = {
|
||||
'amount': {
|
||||
'min': minAmount,
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': minPrice,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
};
|
||||
result.push ({
|
||||
'info': market,
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': undefined,
|
||||
'maker': fees['maker'],
|
||||
'taker': fees['taker'],
|
||||
'limits': limits,
|
||||
'precision': precision,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const balances = await this.privateGetAccountBalance (params);
|
||||
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 multiplier = 100000000;
|
||||
let total = this.safeFloat (balance, 'balance');
|
||||
if (total !== undefined) {
|
||||
total /= multiplier;
|
||||
}
|
||||
let used = this.safeFloat (balance, 'pendingFunds');
|
||||
if (used !== undefined) {
|
||||
used /= multiplier;
|
||||
}
|
||||
const account = this.account ();
|
||||
account['used'] = used;
|
||||
account['total'] = total;
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
//
|
||||
// {
|
||||
// "timestamp":1572307200000,
|
||||
// "open":1962218,
|
||||
// "high":1974850,
|
||||
// "low":1962208,
|
||||
// "close":1974850,
|
||||
// "volume":305211315,
|
||||
// }
|
||||
//
|
||||
const multiplier = 100000000; // for price and volume
|
||||
const keys = [ 'open', 'high', 'low', 'close', 'volume' ];
|
||||
const result = [
|
||||
this.safeInteger (ohlcv, 'timestamp'),
|
||||
];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
let value = this.safeFloat (ohlcv, key);
|
||||
if (value !== undefined) {
|
||||
value = value / multiplier;
|
||||
}
|
||||
result.push (value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.load_markets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
'timeframe': this.timeframes[timeframe],
|
||||
// set to true to see candles more recent than the timestamp in the
|
||||
// since parameter, if a since parameter is used, default is false
|
||||
'indexForward': true,
|
||||
// set to true to see the earliest candles first in the list of
|
||||
// returned candles in chronological order, default is false
|
||||
'sortForward': true,
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['since'] = since;
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit; // default is 3000
|
||||
}
|
||||
const response = await this.publicGetV2MarketIdTickByTimeTimeframe (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "success":true,
|
||||
// "paging":{
|
||||
// "newer":"/v2/market/ETH/BTC/tickByTime/day?indexForward=true&since=1572307200000",
|
||||
// "older":"/v2/market/ETH/BTC/tickByTime/day?since=1457827200000"
|
||||
// },
|
||||
// "ticks":[
|
||||
// {"timestamp":1572307200000,"open":1962218,"high":1974850,"low":1962208,"close":1974850,"volume":305211315},
|
||||
// {"timestamp":1572220800000,"open":1924700,"high":1951276,"low":1909328,"close":1951276,"volume":1086067595},
|
||||
// {"timestamp":1572134400000,"open":1962155,"high":1962734,"low":1900905,"close":1930243,"volume":790141098},
|
||||
// ],
|
||||
// }
|
||||
//
|
||||
const ticks = this.safeValue (response, 'ticks', []);
|
||||
return this.parseOHLCVs (ticks, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketIdOrderbook (this.extend (request, params));
|
||||
const timestamp = this.safeTimestamp (response, 'timestamp');
|
||||
return this.parseOrderBook (response, timestamp);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (ticker, 'timestamp');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'lastPrice');
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': undefined,
|
||||
'low': undefined,
|
||||
'bid': this.safeFloat (ticker, 'bestBid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'bestAsk'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': undefined,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': undefined,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'volume24h'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketIdTick (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 = amount * price;
|
||||
}
|
||||
}
|
||||
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 = {
|
||||
// 'since': 59868345231,
|
||||
'id': market['id'],
|
||||
};
|
||||
const response = await this.publicGetMarketIdTrades (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 multiplier = 100000000; // for price and volume
|
||||
const orderSide = (side === 'buy') ? 'Bid' : 'Ask';
|
||||
const request = this.ordered ({
|
||||
'currency': market['quote'],
|
||||
});
|
||||
request['currency'] = market['quote'];
|
||||
request['instrument'] = market['base'];
|
||||
request['price'] = parseInt (this.decimalToPrecision (price * multiplier, ROUND, 0));
|
||||
request['volume'] = parseInt (this.decimalToPrecision (amount * multiplier, ROUND, 0));
|
||||
request['orderSide'] = orderSide;
|
||||
request['ordertype'] = this.capitalize (type);
|
||||
request['clientRequestId'] = this.nonce ().toString ();
|
||||
const response = await this.privatePostOrderCreate (this.extend (request, params));
|
||||
const id = this.safeString (response, 'id');
|
||||
return {
|
||||
'info': response,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrders (ids, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
ids[i] = parseInt (ids[i]);
|
||||
}
|
||||
const request = {
|
||||
'orderIds': ids,
|
||||
};
|
||||
return await this.privatePostOrderCancel (this.extend (request, params));
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
return await this.cancelOrders ([ id ]);
|
||||
}
|
||||
|
||||
calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
|
||||
const market = this.markets[symbol];
|
||||
const rate = market[takerOrMaker];
|
||||
let currency = undefined;
|
||||
let cost = undefined;
|
||||
if (market['quote'] === 'AUD') {
|
||||
currency = market['quote'];
|
||||
cost = parseFloat (this.costToPrecision (symbol, amount * price));
|
||||
} else {
|
||||
currency = market['base'];
|
||||
cost = parseFloat (this.amountToPrecision (symbol, amount));
|
||||
}
|
||||
return {
|
||||
'type': takerOrMaker,
|
||||
'currency': currency,
|
||||
'rate': rate,
|
||||
'cost': parseFloat (this.feeToPrecision (symbol, rate * cost)),
|
||||
};
|
||||
}
|
||||
|
||||
parseMyTrade (trade, market) {
|
||||
const multiplier = 100000000;
|
||||
const timestamp = this.safeInteger (trade, 'creationTime');
|
||||
let side = this.safeFloat (trade, 'side');
|
||||
side = (side === 'Bid') ? 'buy' : 'sell';
|
||||
// BTCMarkets always charge in AUD for AUD-related transactions.
|
||||
let feeCurrencyCode = undefined;
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
feeCurrencyCode = (market['quote'] === 'AUD') ? market['quote'] : market['base'];
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const id = this.safeString (trade, 'id');
|
||||
let price = this.safeFloat (trade, 'price');
|
||||
if (price !== undefined) {
|
||||
price /= multiplier;
|
||||
}
|
||||
let amount = this.safeFloat (trade, 'volume');
|
||||
if (amount !== undefined) {
|
||||
amount /= multiplier;
|
||||
}
|
||||
let feeCost = this.safeFloat (trade, 'fee');
|
||||
if (feeCost !== undefined) {
|
||||
feeCost /= multiplier;
|
||||
}
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
const orderId = this.safeString (trade, 'orderId');
|
||||
return {
|
||||
'info': trade,
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'order': orderId,
|
||||
'symbol': symbol,
|
||||
'type': undefined,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'fee': {
|
||||
'currency': feeCurrencyCode,
|
||||
'cost': feeCost,
|
||||
},
|
||||
'takerOrMaker': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
parseMyTrades (trades, market = undefined, since = undefined, limit = undefined) {
|
||||
const result = [];
|
||||
for (let i = 0; i < trades.length; i++) {
|
||||
const trade = this.parseMyTrade (trades[i], market);
|
||||
result.push (trade);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const multiplier = 100000000;
|
||||
const side = (order['orderSide'] === 'Bid') ? 'buy' : 'sell';
|
||||
const type = (order['ordertype'] === 'Limit') ? 'limit' : 'market';
|
||||
const timestamp = this.safeInteger (order, 'creationTime');
|
||||
if (market === undefined) {
|
||||
market = this.market (order['instrument'] + '/' + order['currency']);
|
||||
}
|
||||
let status = 'open';
|
||||
if (order['status'] === 'Failed' || order['status'] === 'Cancelled' || order['status'] === 'Partially Cancelled' || order['status'] === 'Error') {
|
||||
status = 'canceled';
|
||||
} else if (order['status'] === 'Fully Matched' || order['status'] === 'Partially Matched') {
|
||||
status = 'closed';
|
||||
}
|
||||
const price = this.safeFloat (order, 'price') / multiplier;
|
||||
const amount = this.safeFloat (order, 'volume') / multiplier;
|
||||
const remaining = this.safeFloat (order, 'openVolume', 0.0) / multiplier;
|
||||
const filled = amount - remaining;
|
||||
const trades = this.parseMyTrades (order['trades'], market);
|
||||
const numTrades = trades.length;
|
||||
let cost = filled * price;
|
||||
let average = undefined;
|
||||
let lastTradeTimestamp = undefined;
|
||||
if (numTrades > 0) {
|
||||
cost = 0;
|
||||
for (let i = 0; i < numTrades; i++) {
|
||||
const trade = trades[i];
|
||||
cost = this.sum (cost, trade['cost']);
|
||||
}
|
||||
if (filled > 0) {
|
||||
average = cost / filled;
|
||||
}
|
||||
lastTradeTimestamp = trades[numTrades - 1]['timestamp'];
|
||||
}
|
||||
const id = this.safeString (order, 'id');
|
||||
const clientOrderId = this.safeString (order, 'clientRequestId');
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': clientOrderId,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': lastTradeTimestamp,
|
||||
'symbol': market['symbol'],
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'average': average,
|
||||
'status': status,
|
||||
'trades': trades,
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const ids = [ parseInt (id) ];
|
||||
const request = {
|
||||
'orderIds': ids,
|
||||
};
|
||||
const response = await this.privatePostOrderDetail (this.extend (request, params));
|
||||
const numOrders = response['orders'].length;
|
||||
if (numOrders < 1) {
|
||||
throw new OrderNotFound (this.id + ' No matching order found: ' + id);
|
||||
}
|
||||
const order = response['orders'][0];
|
||||
return this.parseOrder (order);
|
||||
}
|
||||
|
||||
createPaginatedRequest (market, since = undefined, limit = undefined) {
|
||||
limit = (limit === undefined) ? 100 : limit;
|
||||
since = (since === undefined) ? 0 : since;
|
||||
const request = this.ordered ({
|
||||
'currency': market['quoteId'],
|
||||
'instrument': market['baseId'],
|
||||
'limit': limit,
|
||||
'since': since,
|
||||
});
|
||||
return request;
|
||||
}
|
||||
|
||||
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 = this.createPaginatedRequest (market, since, limit);
|
||||
const response = await this.privatePostOrderHistory (this.extend (request, params));
|
||||
return this.parseOrders (response['orders'], market);
|
||||
}
|
||||
|
||||
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 = this.createPaginatedRequest (market, since, limit);
|
||||
const response = await this.privatePostOrderOpen (this.extend (request, params));
|
||||
return this.parseOrders (response['orders'], market);
|
||||
}
|
||||
|
||||
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 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 = this.createPaginatedRequest (market, since, limit);
|
||||
const response = await this.privatePostOrderTradeHistory (this.extend (request, params));
|
||||
return this.parseMyTrades (response['trades'], market);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const uri = '/' + this.implodeParams (path, params);
|
||||
let url = this.urls['api'][api] + uri;
|
||||
if (api === 'private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
let auth = undefined;
|
||||
headers = {
|
||||
'apikey': this.apiKey,
|
||||
'timestamp': nonce,
|
||||
};
|
||||
if (method === 'POST') {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
auth = uri + "\n" + nonce + "\n"; // eslint-disable-line quotes
|
||||
body = this.json (params);
|
||||
auth += body;
|
||||
} else {
|
||||
const query = this.keysort (this.omit (params, this.extractParams (path)));
|
||||
let queryString = '';
|
||||
if (Object.keys (query).length) {
|
||||
queryString = this.urlencode (query);
|
||||
url += '?' + queryString;
|
||||
queryString += "\n"; // eslint-disable-line quotes
|
||||
}
|
||||
auth = uri + "\n" + queryString + nonce + "\n"; // eslint-disable-line quotes
|
||||
}
|
||||
const secret = this.base64ToBinary (this.secret);
|
||||
const signature = this.hmac (this.encode (auth), secret, 'sha512', 'base64');
|
||||
headers['signature'] = this.decode (signature);
|
||||
} else {
|
||||
if (Object.keys (params).length) {
|
||||
url += '?' + 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
|
||||
}
|
||||
if ('success' in response) {
|
||||
if (!response['success']) {
|
||||
const error = this.safeString (response, 'errorCode');
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwExactlyMatchedException (this.exceptions, error, feedback);
|
||||
throw new ExchangeError (feedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const coinegg = require ('./coinegg.js');
|
||||
const { ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class btctradeim extends coinegg {
|
||||
describe () {
|
||||
const result = this.deepExtend (super.describe (), {
|
||||
'id': 'btctradeim',
|
||||
'name': 'BtcTrade.im',
|
||||
'countries': [ 'HK' ],
|
||||
'urls': {
|
||||
'referral': 'https://m.baobi.com/invite?inv=1765b2',
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/36770531-c2142444-1c5b-11e8-91e2-a4d90dc85fe8.jpg',
|
||||
'api': {
|
||||
'web': 'https://api.btctrade.im/coin',
|
||||
'rest': 'https://api.btctrade.im/api/v1',
|
||||
},
|
||||
'www': 'https://www.btctrade.im',
|
||||
'doc': 'https://www.btctrade.im/help.api.html',
|
||||
'fees': 'https://www.btctrade.im/spend.price.html',
|
||||
},
|
||||
'status': {
|
||||
'status': 'error',
|
||||
'updated': undefined,
|
||||
'eta': undefined,
|
||||
'url': undefined,
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.2 / 100,
|
||||
'taker': 0.2 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'BTC': 0.001,
|
||||
},
|
||||
},
|
||||
},
|
||||
// see the fix below
|
||||
// 'options': {
|
||||
// 'quoteIds': [ 'btc', 'eth', 'usc' ],
|
||||
// },
|
||||
});
|
||||
// a fix for PHP array_merge not overwriting "lists" (integer-indexed arrays)
|
||||
// https://github.com/ccxt/ccxt/issues/3343
|
||||
result['options']['quoteIds'] = [ 'btc', 'eth', 'usc' ];
|
||||
return result;
|
||||
}
|
||||
|
||||
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 === 'web') {
|
||||
return response;
|
||||
}
|
||||
const data = this.safeValue (response, 'data');
|
||||
if (data) {
|
||||
const code = this.safeString (response, 'code');
|
||||
if (code !== '0') {
|
||||
const message = this.safeString (response, 'msg', 'Error');
|
||||
throw new ExchangeError (message);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'createMarketOrder': false,
|
||||
'fetchOpenOrders': 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,
|
||||
'side': this.safeString (order, 'type'),
|
||||
'price': this.safeFloat (order, 'price'),
|
||||
'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 };
|
||||
}
|
||||
};
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
'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': {
|
||||
'CORS': true,
|
||||
'fetchTickers': true,
|
||||
'fetchOHLCV': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1d': '1d',
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27992709-18e15646-64a3-11e7-9fa2-b0950ec7712f.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 result;
|
||||
}
|
||||
|
||||
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, timeframe = '1d', since = undefined, limit = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (ohlcv, 'Time'));
|
||||
return [
|
||||
timestamp,
|
||||
this.safeFloat (ohlcv, 'Open'),
|
||||
this.safeFloat (ohlcv, 'High'),
|
||||
this.safeFloat (ohlcv, 'Low'),
|
||||
this.safeFloat (ohlcv, 'Close'),
|
||||
this.safeFloat (ohlcv, 'Volume'),
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '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.stringToBase64 (this.hmac (this.encode (auth), secret, 'sha256', 'binary')),
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'createDepositAddress': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchCurrencies': true,
|
||||
'fetchDepositAddress': true,
|
||||
'fetchDeposits': true,
|
||||
'fetchFundingFees': true,
|
||||
'fetchMyTrades': false,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': true,
|
||||
'fetchTrades': 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'));
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const marketId = order['market_id'];
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const 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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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 (this.encode (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+1185
File diff suppressed because it is too large.
Load diff
+1925
File diff suppressed because it is too large.
Load diff
+1392
File diff suppressed because it is too large.
Load diff
+1219
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
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+1185
File diff suppressed because it is too large.
Load diff
+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',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+961
@@ -0,0 +1,961 @@
|
||||
'use strict';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { InsufficientFunds, ArgumentsRequired, ExchangeError, InvalidOrder, InvalidAddress, AuthenticationError, NotSupported, OrderNotFound, OnMaintenance, RateLimitExceeded } = require ('./base/errors');
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinbasepro extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinbasepro',
|
||||
'name': 'Coinbase Pro',
|
||||
'countries': [ 'US' ],
|
||||
'rateLimit': 1000,
|
||||
'userAgent': this.userAgents['chrome'],
|
||||
'pro': true,
|
||||
'has': {
|
||||
'cancelAllOrders': true,
|
||||
'CORS': true,
|
||||
'deposit': true,
|
||||
'fetchAccounts': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchDepositAddress': true,
|
||||
'createDepositAddress': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrderTrades': true,
|
||||
'fetchOrders': true,
|
||||
'fetchTime': true,
|
||||
'fetchTransactions': true,
|
||||
'withdraw': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': 60,
|
||||
'5m': 300,
|
||||
'15m': 900,
|
||||
'1h': 3600,
|
||||
'6h': 21600,
|
||||
'1d': 86400,
|
||||
},
|
||||
'urls': {
|
||||
'test': {
|
||||
'public': 'https://api-public.sandbox.pro.coinbase.com',
|
||||
'private': 'https://api-public.sandbox.pro.coinbase.com',
|
||||
},
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/41764625-63b7ffde-760a-11e8-996d-a6328fa9347a.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.pro.coinbase.com',
|
||||
'private': 'https://api.pro.coinbase.com',
|
||||
},
|
||||
'www': 'https://pro.coinbase.com/',
|
||||
'doc': 'https://docs.pro.coinbase.com',
|
||||
'fees': [
|
||||
'https://docs.pro.coinbase.com/#fees',
|
||||
'https://support.pro.coinbase.com/customer/en/portal/articles/2945310-fees',
|
||||
],
|
||||
},
|
||||
'requiredCredentials': {
|
||||
'apiKey': true,
|
||||
'secret': true,
|
||||
'password': true,
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'currencies',
|
||||
'products',
|
||||
'products/{id}/book',
|
||||
'products/{id}/candles',
|
||||
'products/{id}/stats',
|
||||
'products/{id}/ticker',
|
||||
'products/{id}/trades',
|
||||
'time',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'get': [
|
||||
'accounts',
|
||||
'accounts/{id}',
|
||||
'accounts/{id}/holds',
|
||||
'accounts/{id}/ledger',
|
||||
'accounts/{id}/transfers',
|
||||
'coinbase-accounts',
|
||||
'coinbase-accounts/{id}/addresses',
|
||||
'fills',
|
||||
'funding',
|
||||
'fees',
|
||||
'orders',
|
||||
'orders/{id}',
|
||||
'otc/orders',
|
||||
'payment-methods',
|
||||
'position',
|
||||
'reports/{id}',
|
||||
'users/self/trailing-volume',
|
||||
],
|
||||
'post': [
|
||||
'conversions',
|
||||
'deposits/coinbase-account',
|
||||
'deposits/payment-method',
|
||||
'coinbase-accounts/{id}/addresses',
|
||||
'funding/repay',
|
||||
'orders',
|
||||
'position/close',
|
||||
'profiles/margin-transfer',
|
||||
'reports',
|
||||
'withdrawals/coinbase',
|
||||
'withdrawals/crypto',
|
||||
'withdrawals/payment-method',
|
||||
],
|
||||
'delete': [
|
||||
'orders',
|
||||
'orders/{id}',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true, // complicated tier system per coin
|
||||
'percentage': true,
|
||||
'maker': 0.5 / 100, // highest fee of all tiers
|
||||
'taker': 0.5 / 100, // highest fee of all tiers
|
||||
},
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': false,
|
||||
'withdraw': {
|
||||
'BCH': 0,
|
||||
'BTC': 0,
|
||||
'LTC': 0,
|
||||
'ETH': 0,
|
||||
'EUR': 0.15,
|
||||
'USD': 25,
|
||||
},
|
||||
'deposit': {
|
||||
'BCH': 0,
|
||||
'BTC': 0,
|
||||
'LTC': 0,
|
||||
'ETH': 0,
|
||||
'EUR': 0.15,
|
||||
'USD': 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {
|
||||
'Insufficient funds': InsufficientFunds,
|
||||
'NotFound': OrderNotFound,
|
||||
'Invalid API Key': AuthenticationError,
|
||||
'invalid signature': AuthenticationError,
|
||||
'Invalid Passphrase': AuthenticationError,
|
||||
'Invalid order id': InvalidOrder,
|
||||
'Private rate limit exceeded': RateLimitExceeded,
|
||||
},
|
||||
'broad': {
|
||||
'Order already done': OrderNotFound,
|
||||
'order not found': OrderNotFound,
|
||||
'price too small': InvalidOrder,
|
||||
'price too precise': InvalidOrder,
|
||||
'under maintenance': OnMaintenance,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetProducts (params);
|
||||
const result = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const market = response[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 symbol = base + '/' + quote;
|
||||
const priceLimits = {
|
||||
'min': this.safeFloat (market, 'quote_increment'),
|
||||
'max': undefined,
|
||||
};
|
||||
const precision = {
|
||||
'amount': this.precisionFromString (this.safeString (market, 'base_increment')),
|
||||
'price': this.precisionFromString (this.safeString (market, 'quote_increment')),
|
||||
};
|
||||
const active = market['status'] === 'online';
|
||||
result.push (this.extend (this.fees['trading'], {
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': this.safeFloat (market, 'base_min_size'),
|
||||
'max': this.safeFloat (market, 'base_max_size'),
|
||||
},
|
||||
'price': priceLimits,
|
||||
'cost': {
|
||||
'min': this.safeFloat (market, 'min_market_funds'),
|
||||
'max': this.safeFloat (market, 'max_market_funds'),
|
||||
},
|
||||
},
|
||||
'active': active,
|
||||
'info': market,
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchAccounts (params = {}) {
|
||||
const response = await this.privateGetAccounts (params);
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// id: '4aac9c60-cbda-4396-9da4-4aa71e95fba0',
|
||||
// currency: 'BTC',
|
||||
// balance: '0.0000000000000000',
|
||||
// available: '0',
|
||||
// hold: '0.0000000000000000',
|
||||
// profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da'
|
||||
// },
|
||||
// {
|
||||
// id: 'f75fa69a-1ad1-4a80-bd61-ee7faa6135a3',
|
||||
// currency: 'USDC',
|
||||
// balance: '0.0000000000000000',
|
||||
// available: '0',
|
||||
// hold: '0.0000000000000000',
|
||||
// profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da'
|
||||
// },
|
||||
// ]
|
||||
//
|
||||
const result = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const account = response[i];
|
||||
const accountId = this.safeString (account, 'id');
|
||||
const currencyId = this.safeString (account, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
result.push ({
|
||||
'id': accountId,
|
||||
'type': undefined,
|
||||
'currency': code,
|
||||
'info': account,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privateGetAccounts (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 = {
|
||||
'free': this.safeFloat (balance, 'available'),
|
||||
'used': this.safeFloat (balance, 'hold'),
|
||||
'total': this.safeFloat (balance, 'balance'),
|
||||
};
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
// level 1 - only the best bid and ask
|
||||
// level 2 - top 50 bids and asks (aggregated)
|
||||
// level 3 - full order book (non aggregated)
|
||||
const request = {
|
||||
'id': this.marketId (symbol),
|
||||
'level': 2, // 1 best bidask, 2 aggregated, 3 full
|
||||
};
|
||||
const response = await this.publicGetProductsIdBook (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "sequence":1924393896,
|
||||
// "bids":[
|
||||
// ["0.01825","24.34811287",2],
|
||||
// ["0.01824","72.5463",3],
|
||||
// ["0.01823","424.54298049",6],
|
||||
// ],
|
||||
// "asks":[
|
||||
// ["0.01826","171.10414904",4],
|
||||
// ["0.01827","22.60427028",1],
|
||||
// ["0.01828","397.46018784",7],
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const orderbook = this.parseOrderBook (response);
|
||||
orderbook['nonce'] = this.safeInteger (response, 'sequence');
|
||||
return orderbook;
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
//
|
||||
// publicGetProductsIdTicker
|
||||
//
|
||||
// {
|
||||
// "trade_id":843439,
|
||||
// "price":"0.997999",
|
||||
// "size":"80.29769",
|
||||
// "time":"2020-01-28T02:13:33.012523Z",
|
||||
// "bid":"0.997094",
|
||||
// "ask":"0.998",
|
||||
// "volume":"1903188.03750000"
|
||||
// }
|
||||
//
|
||||
// publicGetProductsIdStats
|
||||
//
|
||||
// {
|
||||
// "open": "34.19000000",
|
||||
// "high": "95.70000000",
|
||||
// "low": "7.06000000",
|
||||
// "volume": "2.41000000"
|
||||
// }
|
||||
//
|
||||
const timestamp = this.parse8601 (this.safeValue (ticker, 'time'));
|
||||
const bid = this.safeFloat (ticker, 'bid');
|
||||
const ask = this.safeFloat (ticker, 'ask');
|
||||
const last = this.safeFloat (ticker, 'price');
|
||||
const symbol = (market === undefined) ? undefined : market['symbol'];
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat (ticker, 'high'),
|
||||
'low': this.safeFloat (ticker, 'low'),
|
||||
'bid': bid,
|
||||
'bidVolume': undefined,
|
||||
'ask': ask,
|
||||
'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, 'volume'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
};
|
||||
// publicGetProductsIdTicker or publicGetProductsIdStats
|
||||
const method = this.safeString (this.options, 'fetchTickerMethod', 'publicGetProductsIdTicker');
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
//
|
||||
// publicGetProductsIdTicker
|
||||
//
|
||||
// {
|
||||
// "trade_id":843439,
|
||||
// "price":"0.997999",
|
||||
// "size":"80.29769",
|
||||
// "time":"2020-01-28T02:13:33.012523Z",
|
||||
// "bid":"0.997094",
|
||||
// "ask":"0.998",
|
||||
// "volume":"1903188.03750000"
|
||||
// }
|
||||
//
|
||||
// publicGetProductsIdStats
|
||||
//
|
||||
// {
|
||||
// "open": "34.19000000",
|
||||
// "high": "95.70000000",
|
||||
// "low": "7.06000000",
|
||||
// "volume": "2.41000000"
|
||||
// }
|
||||
//
|
||||
return this.parseTicker (response, market);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
//
|
||||
// {
|
||||
// type: 'match',
|
||||
// trade_id: 82047307,
|
||||
// maker_order_id: '0f358725-2134-435e-be11-753912a326e0',
|
||||
// taker_order_id: '252b7002-87a3-425c-ac73-f5b9e23f3caf',
|
||||
// side: 'sell',
|
||||
// size: '0.00513192',
|
||||
// price: '9314.78',
|
||||
// product_id: 'BTC-USD',
|
||||
// sequence: 12038915443,
|
||||
// time: '2020-01-31T20:03:41.158814Z'
|
||||
// }
|
||||
//
|
||||
const timestamp = this.parse8601 (this.safeString2 (trade, 'time', 'created_at'));
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (trade, 'product_id');
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('-');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if ((symbol === undefined) && (market !== undefined)) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
let feeRate = undefined;
|
||||
let feeCurrency = undefined;
|
||||
let takerOrMaker = undefined;
|
||||
if (market !== undefined) {
|
||||
feeCurrency = market['quote'];
|
||||
if ('liquidity' in trade) {
|
||||
takerOrMaker = (trade['liquidity'] === 'T') ? 'taker' : 'maker';
|
||||
feeRate = market[takerOrMaker];
|
||||
}
|
||||
}
|
||||
const feeCost = this.safeFloat2 (trade, 'fill_fees', 'fee');
|
||||
const fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrency,
|
||||
'rate': feeRate,
|
||||
};
|
||||
const type = undefined;
|
||||
const id = this.safeString (trade, 'trade_id');
|
||||
let side = (trade['side'] === 'buy') ? 'sell' : 'buy';
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
// Coinbase Pro returns inverted side to fetchMyTrades vs fetchTrades
|
||||
if (orderId !== undefined) {
|
||||
side = (trade['side'] === 'buy') ? 'buy' : 'sell';
|
||||
}
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'size');
|
||||
return {
|
||||
'id': id,
|
||||
'order': orderId,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'takerOrMaker': takerOrMaker,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'fee': fee,
|
||||
'cost': price * amount,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
// as of 2018-08-23
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'product_id': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.privateGetFills (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'], // fixes issue #2
|
||||
};
|
||||
const response = await this.publicGetProductsIdTrades (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
return [
|
||||
ohlcv[0] * 1000,
|
||||
ohlcv[3],
|
||||
ohlcv[2],
|
||||
ohlcv[1],
|
||||
ohlcv[4],
|
||||
ohlcv[5],
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const granularity = this.timeframes[timeframe];
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
'granularity': granularity,
|
||||
};
|
||||
if (since !== undefined) {
|
||||
request['start'] = this.iso8601 (since);
|
||||
if (limit === undefined) {
|
||||
// https://docs.pro.coinbase.com/#get-historic-rates
|
||||
limit = 300; // max = 300
|
||||
}
|
||||
request['end'] = this.iso8601 (this.sum ((limit - 1) * granularity * 1000, since));
|
||||
}
|
||||
const response = await this.publicGetProductsIdCandles (this.extend (request, params));
|
||||
return this.parseOHLCVs (response, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async fetchTime (params = {}) {
|
||||
const response = await this.publicGetTime (params);
|
||||
return this.parse8601 (this.safeString (response, 'iso'));
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'pending': 'open',
|
||||
'active': 'open',
|
||||
'open': 'open',
|
||||
'done': 'closed',
|
||||
'canceled': 'canceled',
|
||||
'canceling': 'open',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const timestamp = this.parse8601 (this.safeString (order, 'created_at'));
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (order, 'product_id');
|
||||
let quote = undefined;
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('-');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const filled = this.safeFloat (order, 'filled_size');
|
||||
const amount = this.safeFloat (order, 'size', filled);
|
||||
let remaining = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (filled !== undefined) {
|
||||
remaining = amount - filled;
|
||||
}
|
||||
}
|
||||
const cost = this.safeFloat (order, 'executed_value');
|
||||
const feeCost = this.safeFloat (order, 'fill_fees');
|
||||
let fee = undefined;
|
||||
if (feeCost !== undefined) {
|
||||
let feeCurrencyCode = undefined;
|
||||
if (market !== undefined) {
|
||||
feeCurrencyCode = market['quote'];
|
||||
} else if (quote !== undefined) {
|
||||
feeCurrencyCode = quote;
|
||||
}
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrencyCode,
|
||||
'rate': undefined,
|
||||
};
|
||||
}
|
||||
if ((symbol === undefined) && (market !== undefined)) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const id = this.safeString (order, 'id');
|
||||
const type = this.safeString (order, 'type');
|
||||
const side = this.safeString (order, 'side');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'info': order,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'fee': fee,
|
||||
'average': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privateGetOrdersId (this.extend (request, params));
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
const response = await this.privateGetFills (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'status': 'all',
|
||||
};
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['product_id'] = market['id'];
|
||||
}
|
||||
const response = await this.privateGetOrders (this.extend (request, params));
|
||||
return this.parseOrders (response, market, since, limit);
|
||||
}
|
||||
|
||||
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['product_id'] = market['id'];
|
||||
}
|
||||
const response = await this.privateGetOrders (this.extend (request, params));
|
||||
return this.parseOrders (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'status': 'done',
|
||||
};
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
request['product_id'] = market['id'];
|
||||
}
|
||||
const response = await this.privateGetOrders (this.extend (request, params));
|
||||
return this.parseOrders (response, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
// let oid = this.nonce ().toString ();
|
||||
const request = {
|
||||
'product_id': this.marketId (symbol),
|
||||
'side': side,
|
||||
'size': this.amountToPrecision (symbol, amount),
|
||||
'type': type,
|
||||
};
|
||||
if (type === 'limit') {
|
||||
request['price'] = this.priceToPrecision (symbol, price);
|
||||
}
|
||||
const response = await this.privatePostOrders (this.extend (request, params));
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
return await this.privateDeleteOrdersId ({ 'id': id });
|
||||
}
|
||||
|
||||
async cancelAllOrders (symbol = undefined, params = {}) {
|
||||
return await this.privateDeleteOrders (params);
|
||||
}
|
||||
|
||||
calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
|
||||
const market = this.markets[symbol];
|
||||
const rate = market[takerOrMaker];
|
||||
const cost = amount * price;
|
||||
const currency = market['quote'];
|
||||
return {
|
||||
'type': takerOrMaker,
|
||||
'currency': currency,
|
||||
'rate': rate,
|
||||
'cost': parseFloat (this.currencyToPrecision (currency, rate * cost)),
|
||||
};
|
||||
}
|
||||
|
||||
async fetchPaymentMethods (params = {}) {
|
||||
return await this.privateGetPaymentMethods (params);
|
||||
}
|
||||
|
||||
async deposit (code, amount, address, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
'amount': amount,
|
||||
};
|
||||
let method = 'privatePostDeposits';
|
||||
if ('payment_method_id' in params) {
|
||||
// deposit from a payment_method, like a bank account
|
||||
method += 'PaymentMethod';
|
||||
} else if ('coinbase_account_id' in params) {
|
||||
// deposit into Coinbase Pro account from a Coinbase account
|
||||
method += 'CoinbaseAccount';
|
||||
} else {
|
||||
// deposit methodotherwise we did not receive a supported deposit location
|
||||
// relevant docs link for the Googlers
|
||||
// https://docs.pro.coinbase.com/#deposits
|
||||
throw new NotSupported (this.id + ' deposit() requires one of `coinbase_account_id` or `payment_method_id` extra params');
|
||||
}
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
if (!response) {
|
||||
throw new ExchangeError (this.id + ' deposit() error: ' + this.json (response));
|
||||
}
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['id'],
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
let method = 'privatePostWithdrawals';
|
||||
if ('payment_method_id' in params) {
|
||||
method += 'PaymentMethod';
|
||||
} else if ('coinbase_account_id' in params) {
|
||||
method += 'CoinbaseAccount';
|
||||
} else {
|
||||
method += 'Crypto';
|
||||
request['crypto_address'] = address;
|
||||
}
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
if (!response) {
|
||||
throw new ExchangeError (this.id + ' withdraw() error: ' + this.json (response));
|
||||
}
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['id'],
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
await this.loadAccounts ();
|
||||
let currency = undefined;
|
||||
let id = this.safeString (params, 'id'); // account id
|
||||
if (id === undefined) {
|
||||
if (code === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency code argument if no account id specified in params');
|
||||
}
|
||||
currency = this.currency (code);
|
||||
const accountsByCurrencyCode = this.indexBy (this.accounts, 'currency');
|
||||
const account = this.safeValue (accountsByCurrencyCode, code);
|
||||
if (account === undefined) {
|
||||
throw new ExchangeError (this.id + ' fetchTransactions() could not find account id for ' + code);
|
||||
}
|
||||
id = account['id'];
|
||||
}
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit;
|
||||
}
|
||||
const response = await this.privateGetAccountsIdTransfers (this.extend (request, params));
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
response[i]['currency'] = code;
|
||||
}
|
||||
return this.parseTransactions (response, currency, since, limit);
|
||||
}
|
||||
|
||||
parseTransactionStatus (transaction) {
|
||||
const canceled = this.safeValue (transaction, 'canceled_at');
|
||||
if (canceled) {
|
||||
return 'canceled';
|
||||
}
|
||||
const processed = this.safeValue (transaction, 'processed_at');
|
||||
const completed = this.safeValue (transaction, 'completed_at');
|
||||
if (completed) {
|
||||
return 'ok';
|
||||
} else if (processed && !completed) {
|
||||
return 'failed';
|
||||
} else {
|
||||
return 'pending';
|
||||
}
|
||||
}
|
||||
|
||||
parseTransaction (transaction, currency = undefined) {
|
||||
const details = this.safeValue (transaction, 'details', {});
|
||||
const id = this.safeString (transaction, 'id');
|
||||
const txid = this.safeString (details, 'crypto_transaction_hash');
|
||||
const timestamp = this.parse8601 (this.safeString (transaction, 'created_at'));
|
||||
const updated = this.parse8601 (this.safeString (transaction, 'processed_at'));
|
||||
const currencyId = this.safeString (transaction, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const fee = undefined;
|
||||
const status = this.parseTransactionStatus (transaction);
|
||||
const amount = this.safeFloat (transaction, 'amount');
|
||||
let type = this.safeString (transaction, 'type');
|
||||
let address = this.safeString (details, 'crypto_address');
|
||||
const tag = this.safeString (details, 'destination_tag');
|
||||
address = this.safeString (transaction, 'crypto_address', address);
|
||||
if (type === 'withdraw') {
|
||||
type = 'withdrawal';
|
||||
address = this.safeString (details, 'sent_to_address', address);
|
||||
}
|
||||
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': updated,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
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 (method === 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
request += '?' + this.urlencode (query);
|
||||
}
|
||||
}
|
||||
const url = this.urls['api'][api] + request;
|
||||
if (api === 'private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ().toString ();
|
||||
let payload = '';
|
||||
if (method !== 'GET') {
|
||||
if (Object.keys (query).length) {
|
||||
body = this.json (query);
|
||||
payload = body;
|
||||
}
|
||||
}
|
||||
const what = nonce + method + request + payload;
|
||||
const secret = this.base64ToBinary (this.secret);
|
||||
const signature = this.hmac (this.encode (what), secret, 'sha256', 'base64');
|
||||
headers = {
|
||||
'CB-ACCESS-KEY': this.apiKey,
|
||||
'CB-ACCESS-SIGN': this.decode (signature),
|
||||
'CB-ACCESS-TIMESTAMP': nonce,
|
||||
'CB-ACCESS-PASSPHRASE': this.password,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
let accounts = this.safeValue (this.options, 'coinbaseAccounts');
|
||||
if (accounts === undefined) {
|
||||
accounts = await this.privateGetCoinbaseAccounts ();
|
||||
this.options['coinbaseAccounts'] = accounts; // cache it
|
||||
this.options['coinbaseAccountsByCurrencyId'] = this.indexBy (accounts, 'currency');
|
||||
}
|
||||
const currencyId = currency['id'];
|
||||
const account = this.safeValue (this.options['coinbaseAccountsByCurrencyId'], currencyId);
|
||||
if (account === undefined) {
|
||||
// eslint-disable-next-line quotes
|
||||
throw new InvalidAddress (this.id + " fetchDepositAddress() could not find currency code " + code + " with id = " + currencyId + " in this.options['coinbaseAccountsByCurrencyId']");
|
||||
}
|
||||
const request = {
|
||||
'id': account['id'],
|
||||
};
|
||||
const response = await this.privateGetCoinbaseAccountsIdAddresses (this.extend (request, params));
|
||||
const address = this.safeString (response, 'address');
|
||||
const tag = this.safeString (response, 'destination_tag');
|
||||
return {
|
||||
'currency': code,
|
||||
'address': this.checkAddress (address),
|
||||
'tag': tag,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
async createDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
let accounts = this.safeValue (this.options, 'coinbaseAccounts');
|
||||
if (accounts === undefined) {
|
||||
accounts = await this.privateGetCoinbaseAccounts ();
|
||||
this.options['coinbaseAccounts'] = accounts; // cache it
|
||||
this.options['coinbaseAccountsByCurrencyId'] = this.indexBy (accounts, 'currency');
|
||||
}
|
||||
const currencyId = currency['id'];
|
||||
const account = this.safeValue (this.options['coinbaseAccountsByCurrencyId'], currencyId);
|
||||
if (account === undefined) {
|
||||
// eslint-disable-next-line quotes
|
||||
throw new InvalidAddress (this.id + " fetchDepositAddress() could not find currency code " + code + " with id = " + currencyId + " in this.options['coinbaseAccountsByCurrencyId']");
|
||||
}
|
||||
const request = {
|
||||
'id': account['id'],
|
||||
};
|
||||
const response = await this.privatePostCoinbaseAccountsIdAddresses (this.extend (request, params));
|
||||
const address = this.safeString (response, 'address');
|
||||
const tag = this.safeString (response, 'destination_tag');
|
||||
return {
|
||||
'currency': code,
|
||||
'address': this.checkAddress (address),
|
||||
'tag': tag,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if ((code === 400) || (code === 404)) {
|
||||
if (body[0] === '{') {
|
||||
const message = this.safeString (response, 'message');
|
||||
const feedback = this.id + ' ' + message;
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback);
|
||||
this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback);
|
||||
throw new ExchangeError (feedback); // unknown message
|
||||
}
|
||||
throw new ExchangeError (this.id + ' ' + body);
|
||||
}
|
||||
}
|
||||
|
||||
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') {
|
||||
if ('message' in response) {
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchMyTrades': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27766464-3b5c3c74-5ed9-11e7-840e-31b32968e1da.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');
|
||||
let symbol = undefined;
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
symbol = market['symbol'];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'amount': amount,
|
||||
'remaining': remaining,
|
||||
'filled': filled,
|
||||
'side': side,
|
||||
'type': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'price': price,
|
||||
'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));
|
||||
}
|
||||
};
|
||||
+505
@@ -0,0 +1,505 @@
|
||||
'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': {
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOpenOrders': 'emulated',
|
||||
'fetchMyTrades': false,
|
||||
'fetchTickers': false,
|
||||
},
|
||||
'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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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);
|
||||
this.orders[id] = order;
|
||||
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));
|
||||
return this.parseOrder (response['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));
|
||||
return this.parseOrders (response['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);
|
||||
}
|
||||
};
|
||||
+913
@@ -0,0 +1,913 @@
|
||||
'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': {
|
||||
'fetchTickers': true,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchMyTrades': true,
|
||||
'withdraw': true,
|
||||
'fetchDeposits': true,
|
||||
'fetchWithdrawals': 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/1294454/38046312-0b450aac-32c8-11e8-99ab-bc6b136b6cc7.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];
|
||||
let symbol = marketId;
|
||||
let market = undefined;
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const ticker = {
|
||||
'date': timestamp,
|
||||
'ticker': tickers[marketId],
|
||||
};
|
||||
result[symbol] = this.parseTicker (ticker, market);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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');
|
||||
market = this.safeValue (this.markets_by_id, marketId, market);
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
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, timeframe = '5m', since = undefined, limit = undefined) {
|
||||
return [
|
||||
ohlcv[0] * 1000,
|
||||
parseFloat (ohlcv[1]),
|
||||
parseFloat (ohlcv[3]),
|
||||
parseFloat (ohlcv[4]),
|
||||
parseFloat (ohlcv[2]),
|
||||
parseFloat (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],
|
||||
};
|
||||
const response = await this.publicGetMarketKline (this.extend (request, params));
|
||||
return this.parseOHLCVs (response['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.safeValue (this.markets_by_id, marketId);
|
||||
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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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 order = this.parseOrder (response['data'], market);
|
||||
const id = order['id'];
|
||||
this.orders[id] = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
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));
|
||||
return this.parseOrder (response['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"
|
||||
// }
|
||||
//
|
||||
return this.parseOrder (response['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));
|
||||
return this.parseOrders (response['data']['data'], 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));
|
||||
return this.parseTrades (response['data']['data'], 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': '1', // '1' = normal onchain transfer, '2' = internal local transfer from one user to another
|
||||
};
|
||||
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": [
|
||||
// {
|
||||
// "actual_amount": "1.00000000",
|
||||
// "amount": "1.00000000",
|
||||
// "coin_address": "1KAv3pazbTk2JnQ5xTo6fpKK7p1it2RzD4",
|
||||
// "coin_type": "BCH",
|
||||
// "coin_withdraw_id": 206,
|
||||
// "confirmations": 0,
|
||||
// "create_time": 1524228297,
|
||||
// "status": "audit",
|
||||
// "tx_fee": "0",
|
||||
// "tx_id": ""
|
||||
// },
|
||||
// {
|
||||
// "actual_amount": "0.10000000",
|
||||
// "amount": "0.10000000",
|
||||
// "coin_address": "15sr1VdyXQ6sVLqeJUJ1uPzLpmQtgUeBSB",
|
||||
// "coin_type": "BCH",
|
||||
// "coin_withdraw_id": 203,
|
||||
// "confirmations": 11,
|
||||
// "create_time": 1515806440,
|
||||
// "status": "finish",
|
||||
// "tx_fee": "0",
|
||||
// "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6"
|
||||
// },
|
||||
// {
|
||||
// "actual_amount": "0.00100000",
|
||||
// "amount": "0.00100000",
|
||||
// "coin_address": "1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF",
|
||||
// "coin_type": "BCH",
|
||||
// "coin_withdraw_id": 27,
|
||||
// "confirmations": 0,
|
||||
// "create_time": 1513933541,
|
||||
// "status": "cancel",
|
||||
// "tx_fee": "0",
|
||||
// "tx_id": ""
|
||||
// }
|
||||
// ],
|
||||
// "message": "Ok"
|
||||
// }
|
||||
//
|
||||
return this.parseTransactions (response['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"
|
||||
// }
|
||||
//
|
||||
return this.parseTransactions (response['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;
|
||||
}
|
||||
};
|
||||
+449
@@ -0,0 +1,449 @@
|
||||
'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': {
|
||||
'fetchTickers': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchMyTrades': 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) {
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (ticker, 'name');
|
||||
market = this.safeValue (this.markets_by_id, marketId, market);
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const 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 result;
|
||||
}
|
||||
|
||||
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));
|
||||
return this.parseOrderBook (response['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));
|
||||
return this.parseTrades (response['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));
|
||||
return this.parseTrades (response['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"
|
||||
// }
|
||||
//
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (order, 'market');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const 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 = parseFloat (this.amountToPrecision (symbol, amount - filled));
|
||||
}
|
||||
if (price !== undefined) {
|
||||
cost = parseFloat (this.priceToPrecision (symbol, 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,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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 order = this.parseOrder (response['data'], market);
|
||||
const id = order['id'];
|
||||
this.orders[id] = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
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);
|
||||
return this.parseOrder (response['data'], market);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': id,
|
||||
};
|
||||
const response = await this.privateGetUserOrdersId (this.extend (request, params));
|
||||
return this.parseOrder (response['data']);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
if (symbol !== undefined) {
|
||||
request['market'] = this.marketId (symbol);
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['since_time'] = this.iso8601 (this.milliseconds ());
|
||||
}
|
||||
// TODO: test status=all if it works for closed orders too
|
||||
const response = await this.privateGetUserOrders (this.extend (request, params));
|
||||
return this.parseOrders (response['data']);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
+521
@@ -0,0 +1,521 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'fetchOpenOrders': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/28246081-623fc164-6a1c-11e7-913f-bac0d5576c90.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;
|
||||
}
|
||||
return await this[method] (this.extend (request, params));
|
||||
}
|
||||
|
||||
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',
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'filled': undefined,
|
||||
'remaining': undefined,
|
||||
'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 (this.encode (auth)));
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Basic ' + signature,
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'fetchTickers': 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 result;
|
||||
}
|
||||
|
||||
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');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
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, // 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;
|
||||
}
|
||||
};
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
'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': {
|
||||
'CORS': true,
|
||||
'privateAPI': false,
|
||||
'createOrder': false,
|
||||
'createMarketOrder': false,
|
||||
'createLimitOrder': false,
|
||||
'cancelOrder': false,
|
||||
'editOrder': false,
|
||||
'fetchBalance': false,
|
||||
'fetchOrderBook': false,
|
||||
'fetchL2OrderBook': false,
|
||||
'fetchOHLCV': false,
|
||||
'fetchTrades': false,
|
||||
'fetchTickers': true,
|
||||
'fetchCurrencies': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/28244244-9be6312a-69ed-11e7-99c1-7c1797275265.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',
|
||||
'Bitgem': 'Bitgem',
|
||||
'BlazeCoin': 'BlazeCoin',
|
||||
'BlockCAT': 'BlockCAT',
|
||||
'Blocktrade Token': 'Blocktrade Token',
|
||||
'Catcoin': 'Catcoin',
|
||||
'CanYaCoin': 'CanYaCoin', // conflict with CAN (Content and AD Network)
|
||||
'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)
|
||||
'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',
|
||||
'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)
|
||||
'KingN Coin': 'KingN Coin', // conflict with KNC (Kyber Network)
|
||||
'LiteBitcoin': 'LiteBitcoin', // conflict with LBTC (LightningBitcoin)
|
||||
'Maggie': 'Maggie',
|
||||
'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)
|
||||
'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
|
||||
'Themis': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol)
|
||||
'Menlo One': 'Menlo One', // conflict with Harmony (ONE)
|
||||
'BigONE Token': 'BigONE Token', // conflict with Harmony (ONE)
|
||||
};
|
||||
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;
|
||||
}
|
||||
};
|
||||
+779
@@ -0,0 +1,779 @@
|
||||
'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': {
|
||||
'CORS': true,
|
||||
'fetchBalance': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOrder': true,
|
||||
'fetchMyTrades': true,
|
||||
'fetchTransactions': true,
|
||||
'fetchOpenOrders': true,
|
||||
'createOrder': true,
|
||||
'cancelOrder': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27811229-c1efb510-606c-11e7-9a36-84ba2ce412d8.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': currency,
|
||||
},
|
||||
'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"
|
||||
// }
|
||||
//
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (trade, 'currencyPair');
|
||||
let quote = undefined;
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id[marketId]) {
|
||||
market = this.markets_by_id[marketId];
|
||||
quote = market['quote'];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if (symbol === undefined) {
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
}
|
||||
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': quote,
|
||||
};
|
||||
}
|
||||
let takerOrMaker = this.safeString (trade, 'feeType');
|
||||
takerOrMaker = (takerOrMaker === 'MAKER') ? 'maker' : 'taker';
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': 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.safeFloat2 (order, 'originalAmount', 'amount');
|
||||
const remaining = this.safeFloat (order, 'remainingAmount', amount);
|
||||
let status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const type = this.parseOrderType (this.safeString (order, 'orderTradeType'));
|
||||
let filled = undefined;
|
||||
let cost = undefined;
|
||||
if ((amount !== undefined) && (remaining !== undefined)) {
|
||||
filled = Math.min (amount - remaining, 0);
|
||||
if (remaining === 0) {
|
||||
status = 'closed';
|
||||
}
|
||||
if (price !== undefined) {
|
||||
cost = filled * price;
|
||||
}
|
||||
}
|
||||
const average = this.safeFloat (order, 'avgPrice');
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (order, 'currencyPair');
|
||||
if (marketId !== undefined) {
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
} else {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if ((symbol === undefined) && (market !== undefined)) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const clientOrderId = this.safeString (order, 'clientOrderId');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': clientOrderId,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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);
|
||||
}
|
||||
}
|
||||
};
|
||||
+525
@@ -0,0 +1,525 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { BadSymbol, BadRequest, ExchangeError, ArgumentsRequired, InvalidOrder, OrderNotFound, OnMaintenance } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coinone extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coinone',
|
||||
'name': 'CoinOne',
|
||||
'countries': [ 'KR' ], // Korea
|
||||
'rateLimit': 667,
|
||||
'version': 'v2',
|
||||
'has': {
|
||||
'CORS': false,
|
||||
'createMarketOrder': false,
|
||||
'fetchTickers': true,
|
||||
'fetchOrder': true,
|
||||
},
|
||||
'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/',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BCH/KRW': { 'id': 'bch', 'symbol': 'BCH/KRW', 'base': 'BCH', 'quote': 'KRW', 'baseId': 'bch', 'quoteId': 'krw' },
|
||||
'BTC/KRW': { 'id': 'btc', 'symbol': 'BTC/KRW', 'base': 'BTC', 'quote': 'KRW', 'baseId': 'btc', 'quoteId': 'krw' },
|
||||
'BTG/KRW': { 'id': 'btg', 'symbol': 'BTG/KRW', 'base': 'BTG', 'quote': 'KRW', 'baseId': 'btg', 'quoteId': 'krw' },
|
||||
'ETC/KRW': { 'id': 'etc', 'symbol': 'ETC/KRW', 'base': 'ETC', 'quote': 'KRW', 'baseId': 'etc', 'quoteId': 'krw' },
|
||||
'ETH/KRW': { 'id': 'eth', 'symbol': 'ETH/KRW', 'base': 'ETH', 'quote': 'KRW', 'baseId': 'eth', 'quoteId': 'krw' },
|
||||
'IOTA/KRW': { 'id': 'iota', 'symbol': 'IOTA/KRW', 'base': 'IOTA', 'quote': 'KRW', 'baseId': 'iota', 'quoteId': 'krw' },
|
||||
'LTC/KRW': { 'id': 'ltc', 'symbol': 'LTC/KRW', 'base': 'LTC', 'quote': 'KRW', 'baseId': 'ltc', 'quoteId': 'krw' },
|
||||
'OMG/KRW': { 'id': 'omg', 'symbol': 'OMG/KRW', 'base': 'OMG', 'quote': 'KRW', 'baseId': 'omg', 'quoteId': 'krw' },
|
||||
'QTUM/KRW': { 'id': 'qtum', 'symbol': 'QTUM/KRW', 'base': 'QTUM', 'quote': 'KRW', 'baseId': 'qtum', 'quoteId': 'krw' },
|
||||
'XRP/KRW': { 'id': 'xrp', 'symbol': 'XRP/KRW', 'base': 'XRP', 'quote': 'KRW', 'baseId': 'xrp', 'quoteId': 'krw' },
|
||||
'EOS/KRW': { 'id': 'eos', 'symbol': 'EOS/KRW', 'base': 'EOS', 'quote': 'KRW', 'baseId': 'eos', 'quoteId': 'krw' },
|
||||
'DATA/KRW': { 'id': 'data', 'symbol': 'DATA/KRW', 'base': 'DATA', 'quote': 'KRW', 'baseId': 'data', 'quoteId': 'krw' },
|
||||
'ZIL/KRW': { 'id': 'zil', 'symbol': 'ZIL/KRW', 'base': 'ZIL', 'quote': 'KRW', 'baseId': 'zil', 'quoteId': 'krw' },
|
||||
'KNC/KRW': { 'id': 'knc', 'symbol': 'KNC/KRW', 'base': 'KNC', 'quote': 'KRW', 'baseId': 'knc', 'quoteId': 'krw' },
|
||||
'ZRX/KRW': { 'id': 'zrx', 'symbol': 'ZRX/KRW', 'base': 'ZRX', 'quote': 'KRW', 'baseId': 'zrx', 'quoteId': 'krw' },
|
||||
'LUNA/KRW': { 'id': 'luna', 'symbol': 'LUNA/KRW', 'base': 'LUNA', 'quote': 'KRW', 'baseId': 'luna', 'quoteId': 'krw' },
|
||||
'ATOM/KRW': { 'id': 'atom', 'symbol': 'ATOM/KRW', 'base': 'ATOM', 'quote': 'KRW', 'baseId': 'atom', 'quoteId': 'krw' },
|
||||
'VNT/KRW': { 'id': 'vnt', 'symbol': 'VNT/KRW', 'base': 'VNT', 'quote': 'KRW', 'baseId': 'vnt', 'quoteId': 'krw' },
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'percentage': true,
|
||||
'taker': 0.001,
|
||||
'maker': 0.001,
|
||||
'tiers': {
|
||||
'taker': [
|
||||
[0, 0.001],
|
||||
[100000000, 0.0009],
|
||||
[1000000000, 0.0008],
|
||||
[5000000000, 0.0007],
|
||||
[10000000000, 0.0006],
|
||||
[20000000000, 0.0005],
|
||||
[30000000000, 0.0004],
|
||||
[40000000000, 0.0003],
|
||||
[50000000000, 0.0002],
|
||||
],
|
||||
'maker': [
|
||||
[0, 0.001],
|
||||
[100000000, 0.0008],
|
||||
[1000000000, 0.0006],
|
||||
[5000000000, 0.0004],
|
||||
[10000000000, 0.0002],
|
||||
[20000000000, 0],
|
||||
[30000000000, 0],
|
||||
[40000000000, 0],
|
||||
[50000000000, 0],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'405': OnMaintenance, // {"errorCode":"405","status":"maintenance","result":"error"}
|
||||
'104': OrderNotFound,
|
||||
'108': BadSymbol, // {"errorCode":"108","errorMsg":"Unknown CryptoCurrency","result":"error"}
|
||||
'107': BadRequest, // {"errorCode":"107","errorMsg":"Parameter error","result":"error"}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
return this.parseOrderBook (response, undefined, '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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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.milliseconds ();
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
const previousClose = this.safeFloat (ticker, 'yesterday_last');
|
||||
let change = undefined;
|
||||
if (last !== undefined && previousClose !== undefined) {
|
||||
change = previousClose - last;
|
||||
}
|
||||
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': this.safeFloat (ticker, 'first'),
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': previousClose,
|
||||
'change': change,
|
||||
'percentage': undefined,
|
||||
'average': undefined,
|
||||
'baseVolume': this.safeFloat (ticker, 'volume'),
|
||||
'quoteVolume': undefined,
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeTimestamp (trade, 'timestamp');
|
||||
const symbol = (market !== undefined) ? market['symbol'] : undefined;
|
||||
const is_ask = this.safeString (trade, 'is_ask');
|
||||
let side = undefined;
|
||||
if (is_ask === '1') {
|
||||
side = 'sell';
|
||||
} else if (is_ask === '0') {
|
||||
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;
|
||||
}
|
||||
}
|
||||
return {
|
||||
'id': undefined,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'order': undefined,
|
||||
'symbol': symbol,
|
||||
'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 = {
|
||||
'currency': market['id'],
|
||||
'period': 'hour',
|
||||
'format': 'json',
|
||||
};
|
||||
const response = await this.publicGetTrades (this.extend (request, params));
|
||||
return this.parseTrades (response['completeOrders'], market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
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));
|
||||
let id = this.safeString (response, 'orderId');
|
||||
if (id !== undefined) {
|
||||
id = id.toUpperCase ();
|
||||
}
|
||||
const timestamp = this.milliseconds ();
|
||||
const cost = price * amount;
|
||||
const order = {
|
||||
'info': response,
|
||||
'id': id,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'cost': cost,
|
||||
'average': undefined,
|
||||
'amount': amount,
|
||||
'filled': undefined,
|
||||
'remaining': amount,
|
||||
'status': 'open',
|
||||
'fee': undefined,
|
||||
'clientOrderId': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
this.orders[id] = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let result = undefined;
|
||||
let market = undefined;
|
||||
if (symbol === undefined) {
|
||||
if (id in this.orders) {
|
||||
market = this.market (this.orders[id]['symbol']);
|
||||
} else {
|
||||
throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument for order ids missing in the .orders cache (the order was created with a different instance of this class or within a different run of this code).');
|
||||
}
|
||||
} else {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
try {
|
||||
const request = {
|
||||
'order_id': id,
|
||||
'currency': market['id'],
|
||||
};
|
||||
const response = await this.privatePostOrderOrderInfo (this.extend (request, params));
|
||||
result = this.parseOrder (response);
|
||||
this.orders[id] = result;
|
||||
} catch (e) {
|
||||
if (e instanceof OrderNotFound) {
|
||||
if (id in this.orders) {
|
||||
this.orders[id]['status'] = 'canceled';
|
||||
result = this.orders[id];
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'live': 'open',
|
||||
'partially_filled': 'open',
|
||||
'filled': 'closed',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// {
|
||||
// "index": "0",
|
||||
// "orderId": "68665943-1eb5-4e4b-9d76-845fc54f5489",
|
||||
// "timestamp": "1449037367",
|
||||
// "price": "444000.0",
|
||||
// "qty": "0.3456",
|
||||
// "type": "ask",
|
||||
// "feeRate": "-0.0015"
|
||||
// }
|
||||
//
|
||||
const info = this.safeValue (order, 'info');
|
||||
const id = this.safeStringUpper (info, 'orderId');
|
||||
const timestamp = this.safeTimestamp (info, 'timestamp');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
let cost = undefined;
|
||||
let side = this.safeString (info, 'type');
|
||||
if (side.indexOf ('ask') >= 0) {
|
||||
side = 'sell';
|
||||
} else {
|
||||
side = 'buy';
|
||||
}
|
||||
const price = this.safeFloat (info, 'price');
|
||||
const amount = this.safeFloat (info, 'qty');
|
||||
const remaining = this.safeFloat (info, 'remainQty');
|
||||
let filled = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (remaining !== undefined) {
|
||||
filled = amount - remaining;
|
||||
}
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
const currency = this.safeString (info, 'currency');
|
||||
const fee = {
|
||||
'currency': currency,
|
||||
'cost': this.safeFloat (info, 'fee'),
|
||||
'rate': this.safeFloat (info, 'feeRate'),
|
||||
};
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const marketId = currency.toLowerCase ();
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'side': side,
|
||||
'price': price,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'fee': fee,
|
||||
'average': undefined,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const order = this.safeValue (this.orders, id);
|
||||
let amount = undefined;
|
||||
let price = undefined;
|
||||
let side = undefined;
|
||||
if (order === undefined) {
|
||||
if (symbol === undefined) {
|
||||
// eslint-disable-next-line quotes
|
||||
throw new InvalidOrder (this.id + " cancelOrder could not find the order id " + id + " in orders cache. The order was probably created with a different instance of this class earlier. The `symbol` argument is missing. To cancel the order, pass a symbol argument and {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument of cancelOrder.");
|
||||
}
|
||||
price = this.safeFloat (params, 'price');
|
||||
if (price === undefined) {
|
||||
// eslint-disable-next-line quotes
|
||||
throw new InvalidOrder (this.id + " cancelOrder could not find the order id " + id + " in orders cache. The order was probably created with a different instance of this class earlier. The `price` parameter is missing. To cancel the order, pass a symbol argument and {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument of cancelOrder.");
|
||||
}
|
||||
amount = this.safeFloat (params, 'qty');
|
||||
if (amount === undefined) {
|
||||
// eslint-disable-next-line quotes
|
||||
throw new InvalidOrder (this.id + " cancelOrder could not find the order id " + id + " in orders cache. The order was probably created with a different instance of this class earlier. The `qty` (amount) parameter is missing. To cancel the order, pass a symbol argument and {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument of cancelOrder.");
|
||||
}
|
||||
side = this.safeFloat (params, 'is_ask');
|
||||
if (side === undefined) {
|
||||
// eslint-disable-next-line quotes
|
||||
throw new InvalidOrder (this.id + " cancelOrder could not find the order id " + id + " in orders cache. The order was probably created with a different instance of this class earlier. The `is_ask` (side) parameter is missing. To cancel the order, pass a symbol argument and {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument of cancelOrder.");
|
||||
}
|
||||
} else {
|
||||
price = order['price'];
|
||||
amount = order['amount'];
|
||||
side = (order['side'] === 'buy') ? 0 : 1;
|
||||
symbol = order['symbol'];
|
||||
}
|
||||
const request = {
|
||||
'order_id': id,
|
||||
'price': price,
|
||||
'qty': amount,
|
||||
'is_ask': side,
|
||||
'currency': this.marketId (symbol),
|
||||
};
|
||||
this.orders[id]['status'] = 'canceled';
|
||||
return await this.privatePostOrderCancel (this.extend (request, params));
|
||||
}
|
||||
|
||||
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 (this.encode (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);
|
||||
}
|
||||
}
|
||||
};
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'createMarketOrder': false,
|
||||
},
|
||||
'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',
|
||||
],
|
||||
},
|
||||
},
|
||||
'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));
|
||||
const trades = this.safeValue (response, 'orders', []);
|
||||
return this.parseTrades (trades, market, since, limit);
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const coinegg = require ('./coinegg.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coolcoin extends coinegg {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coolcoin',
|
||||
'name': 'CoolCoin',
|
||||
'countries': [ 'HK' ],
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/36770529-be7b1a04-1c5b-11e8-9600-d11f1996b539.jpg',
|
||||
'api': {
|
||||
'web': 'https://www.coolcoin.com/coin',
|
||||
'rest': 'https://www.coolcoin.com/api/v1',
|
||||
},
|
||||
'www': 'https://www.coolcoin.com',
|
||||
'doc': 'https://www.coolcoin.com/help.api.html',
|
||||
'fees': 'https://www.coolcoin.com/spend.price.html',
|
||||
'referral': 'https://www.coolcoin.com/user/register?invite_code=bhaega',
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'maker': 0.1 / 100,
|
||||
'taker': 0.1 / 100,
|
||||
},
|
||||
'funding': {
|
||||
'withdraw': {
|
||||
'BTC': 0.001,
|
||||
'BCH': 0.002,
|
||||
'ETH': 0.01,
|
||||
'ETC': 0.01,
|
||||
'LTC': 0.001,
|
||||
'TBC': '1%',
|
||||
'HSR': '1%',
|
||||
'NEO': '1%',
|
||||
'SDC': '1%',
|
||||
'EOS': '1%',
|
||||
'BTM': '1%',
|
||||
'XAS': '1%',
|
||||
'ACT': '1%',
|
||||
'SAK': '1%',
|
||||
'GCS': '1%',
|
||||
'HCC': '1%',
|
||||
'QTUM': '1%',
|
||||
'GEC': '1%',
|
||||
'TRX': '1%',
|
||||
'IFC': '1%',
|
||||
'PAY': '1%',
|
||||
'PGC': '1%',
|
||||
'KTC': '1%',
|
||||
'INT': '1%',
|
||||
'LSK': '0.5%',
|
||||
'SKT': '1%',
|
||||
'SSS': '1%',
|
||||
'BT1': '1%',
|
||||
'BT2': '1%',
|
||||
},
|
||||
},
|
||||
},
|
||||
'options': {
|
||||
'quoteIds': ['btc', 'usdt'],
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+943
@@ -0,0 +1,943 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ArgumentsRequired } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class coss extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'coss',
|
||||
'name': 'COSS',
|
||||
'countries': [ 'SG', 'NL' ],
|
||||
'rateLimit': 1000,
|
||||
'version': 'v1',
|
||||
'certified': false,
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/50328158-22e53c00-0503-11e9-825c-c5cfd79bfa74.jpg',
|
||||
'api': {
|
||||
'trade': 'https://trade.coss.io/c/api/v1',
|
||||
'engine': 'https://engine.coss.io/api/v1',
|
||||
'public': 'https://trade.coss.io/c/api/v1',
|
||||
'web': 'https://trade.coss.io/c', // undocumented
|
||||
'exchange': 'https://exchange.coss.io/api',
|
||||
},
|
||||
'www': 'https://www.coss.io',
|
||||
'doc': 'https://api.coss.io/v1/spec',
|
||||
'referral': 'https://www.coss.io/c/reg?r=OWCMHQVW2Q',
|
||||
},
|
||||
'has': {
|
||||
'fetchTrades': true,
|
||||
'fetchTicker': true,
|
||||
'fetchTickers': true,
|
||||
'fetchMarkets': true,
|
||||
'fetchCurrencies': true,
|
||||
'fetchBalance': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOrderTrades': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchOHLCV': true,
|
||||
'createOrder': true,
|
||||
'cancelOrder': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': '1m',
|
||||
'5m': '5m',
|
||||
'15m': '15m',
|
||||
'30m': '30m',
|
||||
'1h': '1h',
|
||||
'2h': '2h',
|
||||
'4h': '4h',
|
||||
'6h': '6h',
|
||||
'12h': '12h',
|
||||
'1d': '1d',
|
||||
'1w': '1w',
|
||||
},
|
||||
'api': {
|
||||
'exchange': {
|
||||
'get': [
|
||||
'getmarketsummaries',
|
||||
],
|
||||
},
|
||||
'public': {
|
||||
'get': [
|
||||
'market-price',
|
||||
'exchange-info',
|
||||
],
|
||||
},
|
||||
'web': {
|
||||
'get': [
|
||||
'coins/getinfo/all', // undocumented
|
||||
'order/symbols', // undocumented
|
||||
'coins/get_base_list', // undocumented
|
||||
],
|
||||
},
|
||||
'engine': {
|
||||
'get': [
|
||||
'dp',
|
||||
'ht',
|
||||
'cs',
|
||||
],
|
||||
},
|
||||
'trade': {
|
||||
'get': [
|
||||
'ping',
|
||||
'time',
|
||||
'account/balances',
|
||||
'account/details',
|
||||
],
|
||||
'post': [
|
||||
'order/add',
|
||||
'order/details',
|
||||
'order/list/open',
|
||||
'order/list/completed',
|
||||
'order/list/all',
|
||||
'order/trade-detail',
|
||||
],
|
||||
'delete': [
|
||||
'order/cancel',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'percentage': true,
|
||||
'taker': 0.0025,
|
||||
'maker': 0.0,
|
||||
},
|
||||
'funding': {
|
||||
'tierBased': false,
|
||||
'percentage': false,
|
||||
'withdraw': {},
|
||||
'deposit': {},
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'COS': 'COSS',
|
||||
'COSS': 'COSS.io',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetExchangeInfo (params);
|
||||
//
|
||||
// { timezone: "UTC",
|
||||
// server_time: 1545171487108,
|
||||
// rate_limits: [ { type: "REQUESTS",
|
||||
// interval: "MINUTE",
|
||||
// limit: 1000 } ],
|
||||
// base_currencies: [ { currency_code: "BTC", minimum_total_order: "0.0001" },
|
||||
// { currency_code: "USDT", minimum_total_order: "1" },
|
||||
// { currency_code: "EUR", minimum_total_order: "1" } ],
|
||||
// coins: [ { currency_code: "ADI",
|
||||
// name: "Aditus",
|
||||
// minimum_order_amount: "0.00000001" },
|
||||
// ...
|
||||
// { currency_code: "NPXSXEM",
|
||||
// name: "PundiX-XEM",
|
||||
// minimum_order_amount: "0.00000001" } ],
|
||||
// symbols: [ { symbol: "ADI_BTC",
|
||||
// amount_limit_decimal: 0,
|
||||
// price_limit_decimal: 8,
|
||||
// allow_trading: true },
|
||||
// ...
|
||||
// { symbol: "ETH_GUSD",
|
||||
// amount_limit_decimal: 5,
|
||||
// price_limit_decimal: 3,
|
||||
// allow_trading: true } ] }
|
||||
//
|
||||
const result = [];
|
||||
const markets = this.safeValue (response, 'symbols', []);
|
||||
const baseCurrencies = this.safeValue (response, 'base_currencies', []);
|
||||
const baseCurrenciesByIds = this.indexBy (baseCurrencies, 'currency_code');
|
||||
const currencies = this.safeValue (response, 'coins', []);
|
||||
const currenciesByIds = this.indexBy (currencies, 'currency_code');
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const marketId = market['symbol'];
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': this.safeInteger (market, 'amount_limit_decimal'),
|
||||
'price': this.safeInteger (market, 'price_limit_decimal'),
|
||||
};
|
||||
const active = this.safeValue (market, 'allow_trading', false);
|
||||
const baseCurrency = this.safeValue (baseCurrenciesByIds, baseId, {});
|
||||
const minCost = this.safeFloat (baseCurrency, 'minimum_total_order');
|
||||
const currency = this.safeValue (currenciesByIds, baseId, {});
|
||||
const defaultMinAmount = Math.pow (10, -precision['amount']);
|
||||
const minAmount = this.safeFloat (currency, 'minimum_order_amount', defaultMinAmount);
|
||||
result.push ({
|
||||
'symbol': symbol,
|
||||
'id': marketId,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'active': active,
|
||||
'precision': precision,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': minAmount,
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': minCost,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
'info': market,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchCurrencies (params = {}) {
|
||||
const response = await this.webGetCoinsGetinfoAll (params);
|
||||
//
|
||||
// [ { currency_code: "VET",
|
||||
// name: "VeChain",
|
||||
// buy_limit: 0,
|
||||
// sell_limit: 0,
|
||||
// usdt: 0,
|
||||
// transaction_time_limit: 5,
|
||||
// status: "trade",
|
||||
// withdrawn_fee: "0.6",
|
||||
// minimum_withdrawn_amount: "1.2",
|
||||
// minimum_deposit_amount: "0.6",
|
||||
// minimum_order_amount: "0.00000001",
|
||||
// decimal_format: "0.########",
|
||||
// token_type: null, // "erc", "eos", "stellar", "tron", "ripple"...
|
||||
// buy_at: 0,
|
||||
// sell_at: 0,
|
||||
// min_rate: 0,
|
||||
// max_rate: 0,
|
||||
// allow_withdrawn: false,
|
||||
// allow_deposit: false,
|
||||
// explorer_website_mainnet_link: null,
|
||||
// explorer_website_testnet_link: null,
|
||||
// deposit_block_confirmation: "6",
|
||||
// withdraw_block_confirmation: "0",
|
||||
// icon_url: "https://s2.coinmarketcap.com/static/img/coins/32x32/3077.png",
|
||||
// is_fiat: false,
|
||||
// allow_sell: true,
|
||||
// allow_buy: true }]
|
||||
//
|
||||
const result = {};
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const currency = response[i];
|
||||
const currencyId = this.safeString (currency, 'currency_code');
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const name = this.safeString (currency, 'name');
|
||||
const allowBuy = this.safeValue (currency, 'allow_buy');
|
||||
const allowSell = this.safeValue (currency, 'allow_sell');
|
||||
const allowWithdrawals = this.safeValue (currency, 'allow_withdrawn');
|
||||
const allowDeposits = this.safeValue (currency, 'allow_deposit');
|
||||
const active = allowBuy && allowSell && allowWithdrawals && allowDeposits;
|
||||
const fee = this.safeFloat (currency, 'withdrawn_fee');
|
||||
const type = this.safeString (currency, 'token_type');
|
||||
//
|
||||
// decimal_format can be anything...
|
||||
//
|
||||
// 0.########
|
||||
// #.########
|
||||
// 0.##
|
||||
// '' (empty string)
|
||||
// 0.000000
|
||||
// null (undefined)
|
||||
// 0.0000
|
||||
// 0.###
|
||||
//
|
||||
const decimalFormat = this.safeString (currency, 'decimal_format');
|
||||
let precision = 8;
|
||||
if (decimalFormat !== undefined) {
|
||||
const parts = decimalFormat.split ('.');
|
||||
const numParts = parts.length; // transpiler workaround for array lengths
|
||||
if (numParts > 1) {
|
||||
if (parts[1].length > 1) {
|
||||
precision = parts[1].length;
|
||||
}
|
||||
}
|
||||
}
|
||||
result[code] = {
|
||||
'id': currencyId,
|
||||
'code': code,
|
||||
'info': currency,
|
||||
'name': name,
|
||||
'active': active,
|
||||
'fee': fee,
|
||||
'precision': precision,
|
||||
'type': type,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': this.safeFloat (currency, 'minimum_order_amount'),
|
||||
'max': undefined,
|
||||
},
|
||||
'withdraw': {
|
||||
'min': this.safeFloat (currency, 'minimum_withdrawn_amount'),
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.tradeGetAccountBalances (params);
|
||||
//
|
||||
// [ { currency_code: "ETH",
|
||||
// address: "0x6820511d43111a941d3e187b9e36ec64af763bde", // deposit address
|
||||
// total: "0.20399125",
|
||||
// available: "0.20399125",
|
||||
// in_order: "0",
|
||||
// memo: null }, // tag, if any
|
||||
// { currency_code: "ICX",
|
||||
// address: "",
|
||||
// total: "0",
|
||||
// available: "0",
|
||||
// in_order: "0",
|
||||
// memo: null } ]
|
||||
//
|
||||
const result = {};
|
||||
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 total = this.safeFloat (balance, 'total');
|
||||
const used = this.safeFloat (balance, 'in_order');
|
||||
const free = this.safeFloat (balance, 'available');
|
||||
result[code] = {
|
||||
'total': total,
|
||||
'used': used,
|
||||
'free': free,
|
||||
};
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
return [
|
||||
parseInt (ohlcv[0]), // timestamp
|
||||
parseFloat (ohlcv[1]), // Open
|
||||
parseFloat (ohlcv[2]), // High
|
||||
parseFloat (ohlcv[3]), // Low
|
||||
parseFloat (ohlcv[4]), // Close
|
||||
parseFloat (ohlcv[5]), // base Volume
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
'tt': this.timeframes[timeframe],
|
||||
};
|
||||
const response = await this.engineGetCs (this.extend (request, params));
|
||||
//
|
||||
// { tt: "1m",
|
||||
// symbol: "ETH_BTC",
|
||||
// nextTime: 1545138960000,
|
||||
// series: [ [ 1545138960000,
|
||||
// "0.02705000",
|
||||
// "0.02705000",
|
||||
// "0.02705000",
|
||||
// "0.02705000",
|
||||
// "0.00000000" ],
|
||||
// ...
|
||||
// [ 1545168900000,
|
||||
// "0.02684000",
|
||||
// "0.02684000",
|
||||
// "0.02684000",
|
||||
// "0.02684000",
|
||||
// "0.00000000" ] ],
|
||||
// limit: 500 }
|
||||
//
|
||||
return this.parseOHLCVs (response['series'], market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const marketId = this.marketId (symbol);
|
||||
const request = { 'symbol': marketId };
|
||||
// limit argument is not supported on COSS's end
|
||||
const response = await this.engineGetDp (this.extend (request, params));
|
||||
//
|
||||
// { symbol: "COSS_ETH",
|
||||
// asks: [ ["0.00065200", "214.15000000"],
|
||||
// ["0.00065300", "645.45000000"],
|
||||
// ...
|
||||
// ["0.00076400", "380.00000000"],
|
||||
// ["0.00076900", "25.00000000"] ],
|
||||
// limit: 100,
|
||||
// bids: [ ["0.00065100", "666.99000000"],
|
||||
// ["0.00065000", "1171.93000000"],
|
||||
// ...
|
||||
// ["0.00037700", "3300.00000000"],
|
||||
// ["0.00037600", "2010.82000000"] ],
|
||||
// time: 1545180569354 }
|
||||
//
|
||||
const timestamp = this.safeInteger (response, 'time');
|
||||
return this.parseOrderBook (response, timestamp);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
//
|
||||
// { MarketName: "COSS-ETH",
|
||||
// High: 0.00066,
|
||||
// Low: 0.000628,
|
||||
// BaseVolume: 131.09652674,
|
||||
// Last: 0.000636,
|
||||
// TimeStamp: "2018-12-19T05:16:41.369Z",
|
||||
// Volume: 206126.6143710692,
|
||||
// Ask: "0.00063600",
|
||||
// Bid: "0.00063400",
|
||||
// PrevDay: 0.000636 }
|
||||
//
|
||||
const timestamp = this.parse8601 (this.safeString (ticker, 'TimeStamp'));
|
||||
let symbol = undefined;
|
||||
let marketId = this.safeString (ticker, 'MarketName');
|
||||
if (marketId !== undefined) {
|
||||
marketId = marketId.replace ('-', '_');
|
||||
}
|
||||
market = this.safeValue (this.markets_by_id, marketId, market);
|
||||
if (market === undefined) {
|
||||
if (marketId !== undefined) {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.exchangeGetGetmarketsummaries (params);
|
||||
//
|
||||
// { success: true,
|
||||
// message: "",
|
||||
// result: [ { MarketName: "COSS-ETH",
|
||||
// High: 0.00066,
|
||||
// Low: 0.000628,
|
||||
// BaseVolume: 131.09652674,
|
||||
// Last: 0.000636,
|
||||
// TimeStamp: "2018-12-19T05:16:41.369Z",
|
||||
// Volume: 206126.6143710692,
|
||||
// Ask: "0.00063600",
|
||||
// Bid: "0.00063400",
|
||||
// PrevDay: 0.000636 },
|
||||
// ...
|
||||
// { MarketName: "XLM-BTC",
|
||||
// High: 0.0000309,
|
||||
// Low: 0.0000309,
|
||||
// BaseVolume: 0,
|
||||
// Last: 0.0000309,
|
||||
// TimeStamp: "2018-12-19T02:00:02.145Z",
|
||||
// Volume: 0,
|
||||
// Ask: "0.00003300",
|
||||
// Bid: "0.00003090",
|
||||
// PrevDay: 0.0000309 } ],
|
||||
// volumes: [ { CoinName: "ETH", Volume: 668.1928095999999 }, // these are overall exchange volumes
|
||||
// { CoinName: "USD", Volume: 9942.58480324 },
|
||||
// { CoinName: "BTC", Volume: 43.749184570000004 },
|
||||
// { CoinName: "COSS", Volume: 909909.26644574 },
|
||||
// { CoinName: "EUR", Volume: 0 },
|
||||
// { CoinName: "TUSD", Volume: 2613.3395026999997 },
|
||||
// { CoinName: "USDT", Volume: 1017152.07416519 },
|
||||
// { CoinName: "GUSD", Volume: 1.80438 },
|
||||
// { CoinName: "XRP", Volume: 15.95508 },
|
||||
// { CoinName: "GBP", Volume: 0 },
|
||||
// { CoinName: "USDC", Volume: 0 } ],
|
||||
// t: 1545196604371 }
|
||||
//
|
||||
const tickers = this.safeValue (response, 'result', []);
|
||||
const result = {};
|
||||
for (let i = 0; i < tickers.length; i++) {
|
||||
const ticker = this.parseTicker (tickers[i]);
|
||||
const symbol = ticker['symbol'];
|
||||
result[symbol] = ticker;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
const tickers = await this.fetchTickers ([ symbol ], params);
|
||||
return tickers[symbol];
|
||||
}
|
||||
|
||||
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.engineGetHt (this.extend (request, params));
|
||||
//
|
||||
// { symbol: "COSS_ETH",
|
||||
// limit: 100,
|
||||
// history: [ { id: 481321,
|
||||
// price: "0.00065100",
|
||||
// qty: "272.92000000",
|
||||
// isBuyerMaker: false,
|
||||
// time: 1545180845019 },
|
||||
// { id: 481322,
|
||||
// price: "0.00065200",
|
||||
// qty: "1.90000000",
|
||||
// isBuyerMaker: true,
|
||||
// time: 1545180847535 },
|
||||
// ...
|
||||
// { id: 481420,
|
||||
// price: "0.00065300",
|
||||
// qty: "2.00000000",
|
||||
// isBuyerMaker: true,
|
||||
// time: 1545181167702 } ],
|
||||
// time: 1545181171274 }
|
||||
//
|
||||
return this.parseTrades (response['history'], market, since, limit);
|
||||
}
|
||||
|
||||
parseTradeFee (fee) {
|
||||
if (fee === undefined) {
|
||||
return fee;
|
||||
}
|
||||
const parts = fee.split (' ');
|
||||
const numParts = parts.length;
|
||||
const cost = parts[0];
|
||||
let code = undefined;
|
||||
if (numParts > 1) {
|
||||
code = this.safeCurrencyCode (parts[1]);
|
||||
}
|
||||
return {
|
||||
'cost': cost,
|
||||
'currency': code,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
//
|
||||
// fetchTrades (public)
|
||||
//
|
||||
// { id: 481322,
|
||||
// price: "0.00065200",
|
||||
// qty: "1.90000000",
|
||||
// isBuyerMaker: true,
|
||||
// time: 1545180847535 }
|
||||
//
|
||||
// fetchOrderTrades (private)
|
||||
//
|
||||
// [ { hex_id: null,
|
||||
// symbol: "COSS_ETH",
|
||||
// order_id: "ad6f6b47-3def-4add-a5d5-2549a9df1593",
|
||||
// order_side: "BUY",
|
||||
// price: "0.00065900",
|
||||
// quantity: "10",
|
||||
// fee: "0.00700000 COSS",
|
||||
// additional_fee: "0.00000461 ETH",
|
||||
// total: "0.00659000 ETH",
|
||||
// timestamp: 1545152356075 } ]
|
||||
//
|
||||
const id = this.safeString (trade, 'id');
|
||||
const timestamp = this.safeInteger (trade, 'time');
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
const side = this.safeStringLower (trade, 'order_side');
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (trade, 'symbol');
|
||||
if (marketId !== undefined) {
|
||||
market = this.safeValue (this.markets_by_id, marketId, market);
|
||||
if (market === undefined) {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
}
|
||||
} else if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
let cost = undefined;
|
||||
const price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat2 (trade, 'qty', 'quantity');
|
||||
if (amount !== undefined) {
|
||||
if (price !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
const result = {
|
||||
'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,
|
||||
};
|
||||
const fee = this.parseTradeFee (this.safeString (trade, 'fee'));
|
||||
if (fee !== undefined) {
|
||||
const additionalFee = this.parseTradeFee (this.safeString (trade, 'additional_fee'));
|
||||
if (additionalFee === undefined) {
|
||||
result['fee'] = fee;
|
||||
} else {
|
||||
result['fees'] = [
|
||||
fee,
|
||||
additionalFee,
|
||||
];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrdersByType (type, 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 = {
|
||||
// 'from_id': 'b2a2d379-f9b6-418b-9414-cbf8330b20d1', // string (uuid), fetchOrders (all orders) only
|
||||
// 'page': 0, // different pagination in fetchOpenOrders and fetchClosedOrders
|
||||
// 'limit': 50, // optional, max = default = 50
|
||||
'symbol': market['id'], // required
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit'] = limit; // max = default = 50
|
||||
}
|
||||
const method = 'tradePostOrderList' + type;
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
//
|
||||
// fetchOrders, fetchClosedOrders
|
||||
//
|
||||
// [ { hex_id: "5c192784330fe51149f556bb",
|
||||
// order_id: "5e46e1b1-93d5-4656-9b43-a5635b08eae9",
|
||||
// account_id: "a0c20128-b9e0-484e-9bc8-b8bb86340e5b",
|
||||
// order_symbol: "COSS_ETH",
|
||||
// order_side: "BUY",
|
||||
// status: "filled",
|
||||
// createTime: 1545152388019,
|
||||
// type: "limit",
|
||||
// timeMatching: 0,
|
||||
// order_price: "0.00065900",
|
||||
// order_size: "10",
|
||||
// executed: "10",
|
||||
// stop_price: "0.00000000",
|
||||
// avg: "0.00065900",
|
||||
// total: "0.00659000 ETH" } ]
|
||||
//
|
||||
// fetchOpenOrders
|
||||
//
|
||||
// {
|
||||
// "total": 2,
|
||||
// "list": [
|
||||
// {
|
||||
// "order_id": "9e5ae4dd-3369-401d-81f5-dff985e1c4ty",
|
||||
// "account_id": "9e5ae4dd-3369-401d-81f5-dff985e1c4a6",
|
||||
// "order_symbol": "eth-btc",
|
||||
// "order_side": "BUY",
|
||||
// "status": "OPEN",
|
||||
// "createTime": 1538114348750,
|
||||
// "type": "limit",
|
||||
// "order_price": "0.12345678",
|
||||
// "order_size": "10.12345678",
|
||||
// "executed": "0",
|
||||
// "stop_price": "02.12345678",
|
||||
// "avg": "1.12345678",
|
||||
// "total": "2.12345678"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// the following code is to handle the above difference in response formats
|
||||
let orders = undefined;
|
||||
if (Array.isArray (response)) {
|
||||
orders = response;
|
||||
} else {
|
||||
orders = this.safeValue (response, 'list', []);
|
||||
}
|
||||
return this.parseOrders (orders, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchOrdersByType ('All', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchOrdersByType ('Completed', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchOrdersByType ('Open', symbol, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
const response = await this.tradePostOrderDetails (this.extend (request, params));
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
const response = await this.tradePostOrderTradeDetail (this.extend (request, params));
|
||||
//
|
||||
// [ { hex_id: null,
|
||||
// symbol: "COSS_ETH",
|
||||
// order_id: "ad6f6b47-3def-4add-a5d5-2549a9df1593",
|
||||
// order_side: "BUY",
|
||||
// price: "0.00065900",
|
||||
// quantity: "10",
|
||||
// fee: "0.00700000 COSS",
|
||||
// additional_fee: "0.00000461 ETH",
|
||||
// total: "0.00659000 ETH",
|
||||
// timestamp: 1545152356075 } ]
|
||||
//
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
if (status === undefined) {
|
||||
return status;
|
||||
}
|
||||
const statuses = {
|
||||
'OPEN': 'open',
|
||||
'CANCELLED': 'canceled',
|
||||
'FILLED': 'closed',
|
||||
'PARTIAL_FILL': 'closed',
|
||||
'CANCELLING': 'open',
|
||||
};
|
||||
return this.safeString (statuses, status.toUpperCase (), status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// { hex_id: "5c192784330fe51149f556bb", // missing in fetchOpenOrders
|
||||
// order_id: "5e46e1b1-93d5-4656-9b43-a5635b08eae9",
|
||||
// account_id: "a0c20128-b9e0-484e-9bc8-b8bb86340e5b",
|
||||
// order_symbol: "COSS_ETH", // coss-eth in docs
|
||||
// order_side: "BUY",
|
||||
// status: "filled",
|
||||
// createTime: 1545152388019,
|
||||
// type: "limit",
|
||||
// timeMatching: 0, // missing in fetchOpenOrders
|
||||
// order_price: "0.00065900",
|
||||
// order_size: "10",
|
||||
// executed: "10",
|
||||
// stop_price: "0.00000000",
|
||||
// avg: "0.00065900",
|
||||
// total: "0.00659000 ETH" }
|
||||
//
|
||||
const id = this.safeString (order, 'order_id');
|
||||
let symbol = undefined;
|
||||
let marketId = this.safeString (order, 'order_symbol');
|
||||
if (marketId === undefined) {
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
} else {
|
||||
// a minor workaround for lowercase eth-btc symbols
|
||||
marketId = marketId.toUpperCase ();
|
||||
marketId = marketId.replace ('-', '_');
|
||||
market = this.safeValue (this.markets_by_id, marketId, market);
|
||||
if (market === undefined) {
|
||||
const [ baseId, quoteId ] = marketId.split ('_');
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
symbol = base + '/' + quote;
|
||||
} else {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
}
|
||||
const timestamp = this.safeInteger (order, 'createTime');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
const price = this.safeFloat (order, 'order_price');
|
||||
const filled = this.safeFloat (order, 'executed');
|
||||
const type = this.safeString (order, 'type');
|
||||
const amount = this.safeFloat (order, 'order_size');
|
||||
let remaining = undefined;
|
||||
if (amount !== undefined) {
|
||||
if (filled !== undefined) {
|
||||
remaining = amount - filled;
|
||||
}
|
||||
}
|
||||
const average = this.safeFloat (order, 'avg');
|
||||
const side = this.safeStringLower (order, 'order_side');
|
||||
const cost = this.safeFloat (order, 'total');
|
||||
const fee = undefined;
|
||||
const trades = undefined;
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'amount': amount,
|
||||
'cost': cost,
|
||||
'average': average,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'status': status,
|
||||
'fee': fee,
|
||||
'trades': trades,
|
||||
};
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'order_symbol': market['id'],
|
||||
'order_size': this.amountToPrecision (symbol, amount),
|
||||
'order_side': side.toUpperCase (),
|
||||
'type': type,
|
||||
};
|
||||
if (price !== undefined) {
|
||||
request['order_price'] = this.priceToPrecision (symbol, price);
|
||||
}
|
||||
const response = await this.tradePostOrderAdd (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "order_id": "9e5ae4dd-3369-401d-81f5-dff985e1c4ty",
|
||||
// "account_id": "9e5ae4dd-3369-401d-81f5-dff985e1c4a6",
|
||||
// "order_symbol": "eth-btc",
|
||||
// "order_side": "BUY",
|
||||
// "status": "OPEN",
|
||||
// "createTime": 1538114348750,
|
||||
// "type": "limit",
|
||||
// "order_price": "0.12345678",
|
||||
// "order_size": "10.12345678",
|
||||
// "executed": "0",
|
||||
// "stop_price": "02.12345678",
|
||||
// "avg": "1.12345678",
|
||||
// "total": "2.12345678"
|
||||
// }
|
||||
//
|
||||
return this.parseOrder (response, market);
|
||||
}
|
||||
|
||||
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 = {
|
||||
'order_id': id,
|
||||
'order_symbol': market['id'],
|
||||
};
|
||||
const response = await this.tradeDeleteOrderCancel (this.extend (request, params));
|
||||
//
|
||||
// { order_symbol: "COSS_ETH",
|
||||
// order_id: "30f2d698-39a0-4b9f-a3a6-a179542373bd",
|
||||
// order_size: 0,
|
||||
// account_id: "a0c20128-b9e0-484e-9bc8-b8bb86340e5b",
|
||||
// timestamp: 1545202728814,
|
||||
// recvWindow: null }
|
||||
//
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'][api] + '/' + path;
|
||||
if (api === 'trade') {
|
||||
this.checkRequiredCredentials ();
|
||||
const timestamp = this.nonce ();
|
||||
const query = this.extend ({
|
||||
'timestamp': timestamp, // required (int64)
|
||||
// 'recvWindow': 10000, // optional (int32)
|
||||
}, params);
|
||||
let request = undefined;
|
||||
if (method === 'GET') {
|
||||
request = this.urlencode (query);
|
||||
url += '?' + request;
|
||||
} else {
|
||||
request = this.json (query);
|
||||
body = request;
|
||||
}
|
||||
headers = {
|
||||
'Signature': this.hmac (this.encode (request), this.encode (this.secret)),
|
||||
'Authorization': this.apiKey,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
};
|
||||
} else {
|
||||
if (Object.keys (params).length) {
|
||||
url += '?' + this.urlencode (params);
|
||||
}
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
};
|
||||
+1302
File diff suppressed because it is too large.
Load diff
+1712
File diff suppressed because it is too large.
Load diff
+1177
File diff suppressed because it is too large.
Load diff
+1270
File diff suppressed because it is too large.
Load diff
+1447
File diff suppressed because it is too large.
Load diff
+451
@@ -0,0 +1,451 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, AuthenticationError, ExchangeNotAvailable } = 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': {
|
||||
'fetchOrder': true,
|
||||
'fetchTickers': true,
|
||||
'fetchOpenOrders': 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': {
|
||||
'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 result;
|
||||
}
|
||||
|
||||
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',
|
||||
'side': order['type'],
|
||||
'price': price,
|
||||
'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);
|
||||
this.orders[id] = order;
|
||||
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 ();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+732
@@ -0,0 +1,732 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'fetchDepositAddress': false,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchClosedOrders': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOrderBook': true,
|
||||
'fetchOrderBooks': false,
|
||||
'fetchTradingLimits': false,
|
||||
'withdraw': false,
|
||||
'fetchCurrencies': 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];
|
||||
if (id in this.markets_by_id) {
|
||||
market = this.markets_by_id[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
const values = ticker['ticker'];
|
||||
const last = this.safeFloat (values, 0);
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
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 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'));
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (order, 'symbol');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
const 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) {
|
||||
symbol = market['symbol'];
|
||||
feeCurrency = (side === 'buy') ? market['quote'] : market['base'];
|
||||
}
|
||||
feeCost = -feeRebate;
|
||||
} else {
|
||||
feeCost = this.safeFloat (order, 'fill_fees');
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
feeCurrency = (side === 'buy') ? market['base'] : market['quote'];
|
||||
}
|
||||
}
|
||||
return {
|
||||
'info': order,
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'symbol': symbol,
|
||||
'type': orderType,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'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, timeframe = '1m', since = undefined, limit = 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));
|
||||
return this.parseOHLCVs (response['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 (this.encode (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,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/28162465-cd815d4c-67cf-11e7-8e57-438bea0523a2.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));
|
||||
}
|
||||
};
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
'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': {
|
||||
'CORS': false,
|
||||
'createMarketOrder': false,
|
||||
},
|
||||
'rateLimit': 1000,
|
||||
'version': 'v1',
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27991413-11b40d42-647f-11e7-91ee-78ced874dd09.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;
|
||||
}
|
||||
};
|
||||
+1434
File diff suppressed because it is too large.
Load diff
+195
@@ -0,0 +1,195 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class fybse extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'fybse',
|
||||
'name': 'FYB-SE',
|
||||
'countries': [ 'SE' ], // Sweden
|
||||
'has': {
|
||||
'CORS': false,
|
||||
},
|
||||
'rateLimit': 1500,
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27766512-31019772-5edb-11e7-8241-2e675e6797f1.jpg',
|
||||
'api': 'https://www.fybse.se/api/SEK',
|
||||
'www': 'https://www.fybse.se',
|
||||
'doc': 'https://fyb.docs.apiary.io',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'ticker',
|
||||
'tickerdetailed',
|
||||
'orderbook',
|
||||
'trades',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'test',
|
||||
'getaccinfo',
|
||||
'getpendingorders',
|
||||
'getorderhistory',
|
||||
'cancelpendingorder',
|
||||
'placeorder',
|
||||
'withdraw',
|
||||
],
|
||||
},
|
||||
},
|
||||
'markets': {
|
||||
'BTC/SEK': { 'id': 'SEK', 'symbol': 'BTC/SEK', 'base': 'BTC', 'quote': 'SEK' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostGetaccinfo (params);
|
||||
const btc = this.safeFloat (response, 'btcBal');
|
||||
const symbol = this.symbols[0];
|
||||
const quote = this.markets[symbol]['quote'];
|
||||
const lowercase = quote.toLowerCase () + 'Bal';
|
||||
const fiat = this.safeFloat (response, lowercase);
|
||||
const crypto = this.account ();
|
||||
crypto['total'] = btc;
|
||||
const result = { 'BTC': crypto };
|
||||
result[quote] = this.account ();
|
||||
result[quote]['total'] = fiat;
|
||||
result['info'] = response;
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetOrderbook (params);
|
||||
return this.parseOrderBook (response);
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const ticker = await this.publicGetTickerdetailed (params);
|
||||
const timestamp = this.milliseconds ();
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
const volume = this.safeFloat (ticker, 'vol');
|
||||
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': volume,
|
||||
'quoteVolume': undefined,
|
||||
'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 price = this.safeFloat (trade, 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'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 response = await this.publicGetTrades (params);
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'qty': amount,
|
||||
'price': price,
|
||||
'type': side[0].toUpperCase (),
|
||||
};
|
||||
const response = await this.privatePostPlaceorder (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['pending_oid'],
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'orderNo': id,
|
||||
};
|
||||
return await this.privatePostCancelpendingorder (this.extend (request, params));
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = this.urls['api'] + '/' + path;
|
||||
if (api === 'public') {
|
||||
url += '.json';
|
||||
} else {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
body = this.urlencode (this.extend ({ 'timestamp': nonce }, params));
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'key': this.apiKey,
|
||||
'sig': this.hmac (this.encode (body), this.encode (this.secret), 'sha1'),
|
||||
};
|
||||
}
|
||||
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 === 'private') {
|
||||
if ('error' in response) {
|
||||
if (response['error']) {
|
||||
throw new ExchangeError (this.id + ' ' + this.json (response));
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
+876
@@ -0,0 +1,876 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { AuthenticationError, ExchangeError, ArgumentsRequired, InvalidAddress, OrderNotFound, NotSupported, DDoSProtection, InsufficientFunds, InvalidOrder } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class gateio extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'gateio',
|
||||
'name': 'Gate.io',
|
||||
'countries': [ 'CN' ],
|
||||
'version': '2',
|
||||
'rateLimit': 1000,
|
||||
'pro': true,
|
||||
'has': {
|
||||
'CORS': false,
|
||||
'createMarketOrder': false,
|
||||
'fetchTickers': true,
|
||||
'withdraw': true,
|
||||
'fetchDeposits': true,
|
||||
'fetchWithdrawals': true,
|
||||
'fetchTransactions': true,
|
||||
'createDepositAddress': true,
|
||||
'fetchDepositAddress': true,
|
||||
'fetchClosedOrders': false,
|
||||
'fetchOHLCV': true,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchOrderTrades': true,
|
||||
'fetchOrders': true,
|
||||
'fetchOrder': true,
|
||||
'fetchMyTrades': true,
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': 60,
|
||||
'5m': 300,
|
||||
'10m': 600,
|
||||
'15m': 900,
|
||||
'30m': 1800,
|
||||
'1h': 3600,
|
||||
'2h': 7200,
|
||||
'4h': 14400,
|
||||
'6h': 21600,
|
||||
'12h': 43200,
|
||||
'1d': 86400,
|
||||
'1w': 604800,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/31784029-0313c702-b509-11e7-9ccc-bc0da6a0e435.jpg',
|
||||
'api': {
|
||||
'public': 'https://data.gate.io/api',
|
||||
'private': 'https://data.gate.io/api',
|
||||
},
|
||||
'www': 'https://gate.io/',
|
||||
'doc': 'https://gate.io/api2',
|
||||
'fees': [
|
||||
'https://gate.io/fee',
|
||||
'https://support.gate.io/hc/en-us/articles/115003577673',
|
||||
],
|
||||
'referral': 'https://www.gate.io/signup/2436035',
|
||||
},
|
||||
'api': {
|
||||
'public': {
|
||||
'get': [
|
||||
'candlestick2/{id}',
|
||||
'pairs',
|
||||
'marketinfo',
|
||||
'marketlist',
|
||||
'coininfo',
|
||||
'tickers',
|
||||
'ticker/{id}',
|
||||
'orderBook/{id}',
|
||||
'trade/{id}',
|
||||
'tradeHistory/{id}',
|
||||
'tradeHistory/{id}/{tid}',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'balances',
|
||||
'depositAddress',
|
||||
'newAddress',
|
||||
'depositsWithdrawals',
|
||||
'buy',
|
||||
'sell',
|
||||
'cancelOrder',
|
||||
'cancelAllOrders',
|
||||
'getOrder',
|
||||
'openOrders',
|
||||
'tradeHistory',
|
||||
'feelist',
|
||||
'withdraw',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'tierBased': true,
|
||||
'percentage': true,
|
||||
'maker': 0.002,
|
||||
'taker': 0.002,
|
||||
},
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {
|
||||
'4': DDoSProtection,
|
||||
'5': AuthenticationError, // { result: "false", code: 5, message: "Error: invalid key or sign, please re-generate it from your account" }
|
||||
'6': AuthenticationError, // { result: 'false', code: 6, message: 'Error: invalid data ' }
|
||||
'7': NotSupported,
|
||||
'8': NotSupported,
|
||||
'9': NotSupported,
|
||||
'15': DDoSProtection,
|
||||
'16': OrderNotFound,
|
||||
'17': OrderNotFound,
|
||||
'20': InvalidOrder,
|
||||
'21': InsufficientFunds,
|
||||
},
|
||||
// https://gate.io/api2#errCode
|
||||
'errorCodeNames': {
|
||||
'1': 'Invalid request',
|
||||
'2': 'Invalid version',
|
||||
'3': 'Invalid request',
|
||||
'4': 'Too many attempts',
|
||||
'5': 'Invalid sign',
|
||||
'6': 'Invalid sign',
|
||||
'7': 'Currency is not supported',
|
||||
'8': 'Currency is not supported',
|
||||
'9': 'Currency is not supported',
|
||||
'10': 'Verified failed',
|
||||
'11': 'Obtaining address failed',
|
||||
'12': 'Empty params',
|
||||
'13': 'Internal error, please report to administrator',
|
||||
'14': 'Invalid user',
|
||||
'15': 'Cancel order too fast, please wait 1 min and try again',
|
||||
'16': 'Invalid order id or order is already closed',
|
||||
'17': 'Invalid orderid',
|
||||
'18': 'Invalid amount',
|
||||
'19': 'Not permitted or trade is disabled',
|
||||
'20': 'Your order size is too small',
|
||||
'21': 'You don\'t have enough fund',
|
||||
},
|
||||
},
|
||||
'options': {
|
||||
'fetchTradesMethod': 'public_get_tradehistory_id', // 'public_get_tradehistory_id_tid'
|
||||
'limits': {
|
||||
'cost': {
|
||||
'min': {
|
||||
'BTC': 0.0001,
|
||||
'ETH': 0.001,
|
||||
'USDT': 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'commonCurrencies': {
|
||||
'BTCBEAR': 'BEAR',
|
||||
'BTCBULL': 'BULL',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const response = await this.publicGetMarketinfo (params);
|
||||
const markets = this.safeValue (response, 'pairs');
|
||||
if (!markets) {
|
||||
throw new ExchangeError (this.id + ' fetchMarkets got an unrecognized response');
|
||||
}
|
||||
const result = [];
|
||||
for (let i = 0; i < markets.length; i++) {
|
||||
const market = markets[i];
|
||||
const keys = Object.keys (market);
|
||||
const id = this.safeString (keys, 0);
|
||||
const details = market[id];
|
||||
// all of their symbols are separated with an underscore
|
||||
// but not boe_eth_eth (BOE_ETH/ETH) which has two underscores
|
||||
// https://github.com/ccxt/ccxt/issues/4894
|
||||
const parts = id.split ('_');
|
||||
const numParts = parts.length;
|
||||
let baseId = parts[0];
|
||||
let quoteId = parts[1];
|
||||
if (numParts > 2) {
|
||||
baseId = parts[0] + '_' + parts[1];
|
||||
quoteId = parts[2];
|
||||
}
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': 8,
|
||||
'price': details['decimal_places'],
|
||||
};
|
||||
const amountLimits = {
|
||||
'min': details['min_amount'],
|
||||
'max': undefined,
|
||||
};
|
||||
const priceLimits = {
|
||||
'min': Math.pow (10, -details['decimal_places']),
|
||||
'max': undefined,
|
||||
};
|
||||
const defaultCost = amountLimits['min'] * priceLimits['min'];
|
||||
const minCost = this.safeFloat (this.options['limits']['cost']['min'], quote, defaultCost);
|
||||
const costLimits = {
|
||||
'min': minCost,
|
||||
'max': undefined,
|
||||
};
|
||||
const limits = {
|
||||
'amount': amountLimits,
|
||||
'price': priceLimits,
|
||||
'cost': costLimits,
|
||||
};
|
||||
const active = true;
|
||||
result.push ({
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'info': market,
|
||||
'active': active,
|
||||
'maker': details['fee'] / 100,
|
||||
'taker': details['fee'] / 100,
|
||||
'precision': precision,
|
||||
'limits': limits,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostBalances (params);
|
||||
const result = { 'info': response };
|
||||
let available = this.safeValue (response, 'available', {});
|
||||
if (Array.isArray (available)) {
|
||||
available = {};
|
||||
}
|
||||
const locked = this.safeValue (response, 'locked', {});
|
||||
const currencyIds = Object.keys (available);
|
||||
for (let i = 0; i < currencyIds.length; i++) {
|
||||
const currencyId = currencyIds[i];
|
||||
const code = this.safeCurrencyCode (currencyId);
|
||||
const account = this.account ();
|
||||
account['free'] = this.safeFloat (available, currencyId);
|
||||
account['used'] = this.safeFloat (locked, currencyId);
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'id': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.publicGetOrderBookId (this.extend (request, params));
|
||||
return this.parseOrderBook (response);
|
||||
}
|
||||
|
||||
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
|
||||
// they return [ Timestamp, Volume, Close, High, Low, Open ]
|
||||
return [
|
||||
parseInt (ohlcv[0]), // t
|
||||
parseFloat (ohlcv[5]), // o
|
||||
parseFloat (ohlcv[3]), // h
|
||||
parseFloat (ohlcv[4]), // l
|
||||
parseFloat (ohlcv[2]), // c
|
||||
parseFloat (ohlcv[1]), // v
|
||||
];
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'id': market['id'],
|
||||
'group_sec': this.timeframes[timeframe],
|
||||
};
|
||||
// max limit = 1001
|
||||
if (limit !== undefined) {
|
||||
const periodDurationInSeconds = this.parseTimeframe (timeframe);
|
||||
const hours = parseInt ((periodDurationInSeconds * limit) / 3600);
|
||||
request['range_hour'] = Math.max (0, hours - 1);
|
||||
}
|
||||
const response = await this.publicGetCandlestick2Id (this.extend (request, params));
|
||||
//
|
||||
// {
|
||||
// "elapsed": "15ms",
|
||||
// "result": "true",
|
||||
// "data": [
|
||||
// [ "1553930820000", "1.005299", "4081.05", "4086.18", "4081.05", "4086.18" ],
|
||||
// [ "1553930880000", "0.110923277", "4095.2", "4095.23", "4091.15", "4091.15" ],
|
||||
// ...
|
||||
// [ "1553934420000", "0", "4089.42", "4089.42", "4089.42", "4089.42" ],
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
const data = this.safeValue (response, 'data', []);
|
||||
return this.parseOHLCVs (data, market, timeframe, since, limit);
|
||||
}
|
||||
|
||||
parseTicker (ticker, market = undefined) {
|
||||
const timestamp = this.milliseconds ();
|
||||
let symbol = undefined;
|
||||
if (market) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const last = this.safeFloat (ticker, 'last');
|
||||
const percentage = this.safeFloat (ticker, 'percentChange');
|
||||
let open = undefined;
|
||||
let change = undefined;
|
||||
let average = undefined;
|
||||
if ((last !== undefined) && (percentage !== undefined)) {
|
||||
const relativeChange = percentage / 100;
|
||||
open = last / this.sum (1, relativeChange);
|
||||
change = last - open;
|
||||
average = this.sum (last, open) / 2;
|
||||
}
|
||||
open = this.safeFloat (ticker, 'open', open);
|
||||
change = this.safeFloat (ticker, 'change', change);
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'high': this.safeFloat2 (ticker, 'high24hr', 'high'),
|
||||
'low': this.safeFloat2 (ticker, 'low24hr', 'low'),
|
||||
'bid': this.safeFloat (ticker, 'highestBid'),
|
||||
'bidVolume': undefined,
|
||||
'ask': this.safeFloat (ticker, 'lowestAsk'),
|
||||
'askVolume': undefined,
|
||||
'vwap': undefined,
|
||||
'open': open,
|
||||
'close': last,
|
||||
'last': last,
|
||||
'previousClose': undefined,
|
||||
'change': change,
|
||||
'percentage': percentage,
|
||||
'average': average,
|
||||
'baseVolume': this.safeFloat (ticker, 'quoteVolume'), // gateio has them reversed
|
||||
'quoteVolume': this.safeFloat (ticker, 'baseVolume'),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchTickers (symbols = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.publicGetTickers (params);
|
||||
const result = {};
|
||||
const ids = Object.keys (response);
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
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;
|
||||
let market = undefined;
|
||||
if (symbol in this.markets) {
|
||||
market = this.markets[symbol];
|
||||
}
|
||||
if (id in this.markets_by_id) {
|
||||
market = this.markets_by_id[id];
|
||||
}
|
||||
result[symbol] = this.parseTicker (response[id], market);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const ticker = await this.publicGetTickerId (this.extend ({
|
||||
'id': market['id'],
|
||||
}, params));
|
||||
return this.parseTicker (ticker, market);
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
// {
|
||||
// "tradeID": 3175762,
|
||||
// "date": "2017-08-25 07:24:28",
|
||||
// "type": "sell",
|
||||
// "rate": 29011,
|
||||
// "amount": 0.0019,
|
||||
// "total": 55.1209,
|
||||
// "fee": "0",
|
||||
// "fee_coin": "btc",
|
||||
// "gt_fee":"0",
|
||||
// "point_fee":"0.1213",
|
||||
// },
|
||||
let timestamp = this.safeTimestamp2 (trade, 'timestamp', 'time_unix');
|
||||
timestamp = this.safeTimestamp (trade, 'time', timestamp);
|
||||
const id = this.safeString2 (trade, 'tradeID', 'id');
|
||||
// take either of orderid or orderId
|
||||
const orderId = this.safeString2 (trade, 'orderid', 'orderNumber');
|
||||
const price = this.safeFloat2 (trade, 'rate', 'price');
|
||||
const amount = this.safeFloat (trade, 'amount');
|
||||
const type = this.safeString (trade, 'type');
|
||||
const takerOrMaker = this.safeString (trade, 'role');
|
||||
let cost = undefined;
|
||||
if (price !== undefined) {
|
||||
if (amount !== undefined) {
|
||||
cost = price * amount;
|
||||
}
|
||||
}
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
let fee = undefined;
|
||||
let feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee_coin'));
|
||||
let feeCost = this.safeFloat (trade, 'point_fee');
|
||||
if ((feeCost === undefined) || (feeCost === 0)) {
|
||||
feeCost = this.safeFloat (trade, 'gt_fee');
|
||||
if ((feeCost === undefined) || (feeCost === 0)) {
|
||||
feeCost = this.safeFloat (trade, 'fee');
|
||||
} else {
|
||||
feeCurrency = this.safeCurrencyCode ('GT');
|
||||
}
|
||||
} else {
|
||||
feeCurrency = this.safeCurrencyCode ('POINT');
|
||||
}
|
||||
if (feeCost !== undefined) {
|
||||
fee = {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrency,
|
||||
};
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'order': orderId,
|
||||
'type': undefined,
|
||||
'side': type,
|
||||
'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 = {
|
||||
'id': market['id'],
|
||||
};
|
||||
const method = this.safeString (this.options, 'fetchTradesMethod', 'public_get_tradehistory_id');
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
return this.parseTrades (response['data'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
const response = await this.privatePostOpenOrders (params);
|
||||
return this.parseOrders (response['orders'], undefined, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'orderNumber': id,
|
||||
'currencyPair': this.marketId (symbol),
|
||||
};
|
||||
const response = await this.privatePostGetOrder (this.extend (request, params));
|
||||
return this.parseOrder (response['order']);
|
||||
}
|
||||
|
||||
parseOrderStatus (status) {
|
||||
const statuses = {
|
||||
'cancelled': 'canceled',
|
||||
// 'closed': 'closed', // these two statuses aren't actually needed
|
||||
// 'open': 'open', // as they are mapped one-to-one
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
//
|
||||
// createOrder
|
||||
//
|
||||
// {
|
||||
// "fee": "0 ZEC",
|
||||
// "code": 0,
|
||||
// "rate": "0.0055",
|
||||
// "side": 2,
|
||||
// "type": "buy",
|
||||
// "ctime": 1586460839.138,
|
||||
// "market": "ZEC_BTC",
|
||||
// "result": "true",
|
||||
// "status": "open",
|
||||
// "iceberg": "0",
|
||||
// "message": "Success",
|
||||
// "feeValue": "0",
|
||||
// "filledRate": "0.005500000",
|
||||
// "leftAmount": "0.60607456",
|
||||
// "feeCurrency": "ZEC",
|
||||
// "orderNumber": 10755887009,
|
||||
// "filledAmount": "0",
|
||||
// "feePercentage": 0.002,
|
||||
// "initialAmount": "0.60607456"
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// 'amount': '0.00000000',
|
||||
// 'currencyPair': 'xlm_usdt',
|
||||
// 'fee': '0.0113766632239302 USDT',
|
||||
// 'feeCurrency': 'USDT',
|
||||
// 'feePercentage': 0.18,
|
||||
// 'feeValue': '0.0113766632239302',
|
||||
// 'filledAmount': '30.14004987',
|
||||
// 'filledRate': 0.2097,
|
||||
// 'initialAmount': '30.14004987',
|
||||
// 'initialRate': '0.2097',
|
||||
// 'left': 0,
|
||||
// 'orderNumber': '998307286',
|
||||
// 'rate': '0.2097',
|
||||
// 'status': 'closed',
|
||||
// 'timestamp': 1531158583,
|
||||
// 'type': 'sell'
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "orderNumber": 10802237760,
|
||||
// "orderType": 1,
|
||||
// "type": "buy",
|
||||
// "rate": "0.54250000",
|
||||
// "amount": "45.55638518",
|
||||
// "total": "24.71433896",
|
||||
// "initialRate": "0.54250000",
|
||||
// "initialAmount": "45.55638518",
|
||||
// "filledRate": "0.54250000",
|
||||
// "filledAmount": "0",
|
||||
// "currencyPair": "nano_usdt",
|
||||
// "timestamp": 1586556143,
|
||||
// "status": "open"
|
||||
// }
|
||||
//
|
||||
const id = this.safeString2 (order, 'orderNumber', 'id');
|
||||
let symbol = undefined;
|
||||
const marketId = this.safeString (order, 'currencyPair');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const timestamp = this.safeTimestamp2 (order, 'timestamp', 'ctime');
|
||||
const lastTradeTimestamp = this.safeTimestamp (order, 'mtime');
|
||||
const status = this.parseOrderStatus (this.safeString (order, 'status'));
|
||||
let side = this.safeString (order, 'type');
|
||||
// handling for order.update messages
|
||||
if (side === '1') {
|
||||
side = 'sell';
|
||||
} else if (side === '2') {
|
||||
side = 'buy';
|
||||
}
|
||||
const price = this.safeFloat2 (order, 'initialRate', 'rate');
|
||||
const average = this.safeFloat (order, 'filledRate');
|
||||
const amount = this.safeFloat2 (order, 'initialAmount', 'amount');
|
||||
const filled = this.safeFloat (order, 'filledAmount');
|
||||
// In the order status response, this field has a different name.
|
||||
let remaining = this.safeFloat2 (order, 'leftAmount', 'left');
|
||||
if (remaining === undefined) {
|
||||
remaining = amount - filled;
|
||||
}
|
||||
const feeCost = this.safeFloat (order, 'feeValue');
|
||||
const feeCurrencyId = this.safeString (order, 'feeCurrency');
|
||||
const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId);
|
||||
let feeRate = this.safeFloat (order, 'feePercentage');
|
||||
if (feeRate !== undefined) {
|
||||
feeRate = feeRate / 100;
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': undefined,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'timestamp': timestamp,
|
||||
'lastTradeTimestamp': lastTradeTimestamp,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': 'limit',
|
||||
'side': side,
|
||||
'price': price,
|
||||
'cost': undefined,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'average': average,
|
||||
'trades': undefined,
|
||||
'fee': {
|
||||
'cost': feeCost,
|
||||
'currency': feeCurrencyCode,
|
||||
'rate': feeRate,
|
||||
},
|
||||
'info': order,
|
||||
};
|
||||
}
|
||||
|
||||
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 method = 'privatePost' + this.capitalize (side);
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currencyPair': market['id'],
|
||||
'rate': price,
|
||||
'amount': amount,
|
||||
};
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
return this.parseOrder (this.extend ({
|
||||
'status': 'open',
|
||||
'type': side,
|
||||
'initialAmount': amount,
|
||||
}, response), market);
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' cancelOrder requires symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'orderNumber': id,
|
||||
'currencyPair': this.marketId (symbol),
|
||||
};
|
||||
return await this.privatePostCancelOrder (this.extend (request, params));
|
||||
}
|
||||
|
||||
async queryDepositAddress (method, code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
method = 'privatePost' + method + 'Address';
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
};
|
||||
const response = await this[method] (this.extend (request, params));
|
||||
let address = this.safeString (response, 'addr');
|
||||
let tag = undefined;
|
||||
if ((address !== undefined) && (address.indexOf ('address') >= 0)) {
|
||||
throw new InvalidAddress (this.id + ' queryDepositAddress ' + address);
|
||||
}
|
||||
if (code === 'XRP') {
|
||||
const parts = address.split (' ');
|
||||
address = parts[0];
|
||||
tag = parts[1];
|
||||
}
|
||||
return {
|
||||
'currency': currency,
|
||||
'address': address,
|
||||
'tag': tag,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
async createDepositAddress (code, params = {}) {
|
||||
return await this.queryDepositAddress ('New', code, params);
|
||||
}
|
||||
|
||||
async fetchDepositAddress (code, params = {}) {
|
||||
return await this.queryDepositAddress ('Deposit', code, params);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
let market = undefined;
|
||||
if (symbol !== undefined) {
|
||||
market = this.market (symbol);
|
||||
}
|
||||
const response = await this.privatePostOpenOrders (params);
|
||||
return this.parseOrders (response['orders'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchOrderTrades (id, 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 = {
|
||||
'currencyPair': market['id'],
|
||||
'orderNumber': id,
|
||||
};
|
||||
const response = await this.privatePostTradeHistory (this.extend (request, params));
|
||||
return this.parseTrades (response['trades'], market, since, limit);
|
||||
}
|
||||
|
||||
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
if (symbol === undefined) {
|
||||
throw new ArgumentsRequired (this.id + ' fetchMyTrades requires symbol argument');
|
||||
}
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'currencyPair': market['id'],
|
||||
};
|
||||
const response = await this.privatePostTradeHistory (this.extend (request, params));
|
||||
return this.parseTrades (response['trades'], market, 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,
|
||||
'address': address, // Address must exist in you AddressBook in security settings
|
||||
};
|
||||
if (tag !== undefined) {
|
||||
request['address'] += ' ' + tag;
|
||||
}
|
||||
const response = await this.privatePostWithdraw (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
const prefix = (api === 'private') ? (api + '/') : '';
|
||||
let url = this.urls['api'][api] + this.version + '/1/' + prefix + 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 ();
|
||||
const request = { 'nonce': nonce };
|
||||
body = this.urlencode (this.extend (request, query));
|
||||
const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512');
|
||||
headers = {
|
||||
'Key': this.apiKey,
|
||||
'Sign': signature,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
}
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
async fetchTransactionsByType (type = undefined, code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
if (since !== undefined) {
|
||||
request['start'] = since;
|
||||
}
|
||||
const response = await this.privatePostDepositsWithdrawals (this.extend (request, params));
|
||||
let transactions = undefined;
|
||||
if (type === undefined) {
|
||||
const deposits = this.safeValue (response, 'deposits', []);
|
||||
const withdrawals = this.safeValue (response, 'withdraws', []);
|
||||
transactions = this.arrayConcat (deposits, withdrawals);
|
||||
} else {
|
||||
transactions = this.safeValue (response, type, []);
|
||||
}
|
||||
let currency = undefined;
|
||||
if (code !== undefined) {
|
||||
currency = this.currency (code);
|
||||
}
|
||||
return this.parseTransactions (transactions, currency, since, limit);
|
||||
}
|
||||
|
||||
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchTransactionsByType (undefined, code, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchTransactionsByType ('deposits', code, since, limit, params);
|
||||
}
|
||||
|
||||
async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
return await this.fetchTransactionsByType ('withdraws', code, since, limit, params);
|
||||
}
|
||||
|
||||
parseTransaction (transaction, currency = undefined) {
|
||||
//
|
||||
// deposit
|
||||
//
|
||||
// {
|
||||
// 'id': 'd16520849',
|
||||
// 'currency': 'NEO',
|
||||
// 'address': False,
|
||||
// 'amount': '1',
|
||||
// 'txid': '01acf6b8ce4d24a....',
|
||||
// 'timestamp': '1553125968',
|
||||
// 'status': 'DONE',
|
||||
// 'type': 'deposit'
|
||||
// }
|
||||
//
|
||||
// withdrawal
|
||||
//
|
||||
// {
|
||||
// 'id': 'w5864259',
|
||||
// 'currency': 'ETH',
|
||||
// 'address': '0x72632f462....',
|
||||
// 'amount': '0.4947',
|
||||
// 'txid': '0x111167d120f736....',
|
||||
// 'timestamp': '1553123688',
|
||||
// 'status': 'DONE',
|
||||
// 'type': 'withdrawal'
|
||||
// }
|
||||
//
|
||||
const currencyId = this.safeString (transaction, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const id = this.safeString (transaction, 'id');
|
||||
const txid = this.safeString (transaction, 'txid');
|
||||
const amount = this.safeFloat (transaction, 'amount');
|
||||
let address = this.safeString (transaction, 'address');
|
||||
if (address === 'false') {
|
||||
address = undefined;
|
||||
}
|
||||
const timestamp = this.safeTimestamp (transaction, 'timestamp');
|
||||
const status = this.parseTransactionStatus (this.safeString (transaction, 'status'));
|
||||
const type = this.parseTransactionType (id[0]);
|
||||
return {
|
||||
'info': transaction,
|
||||
'id': id,
|
||||
'txid': txid,
|
||||
'currency': code,
|
||||
'amount': amount,
|
||||
'address': address,
|
||||
'tag': undefined,
|
||||
'status': status,
|
||||
'type': type,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'fee': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
parseTransactionStatus (status) {
|
||||
const statuses = {
|
||||
'PEND': 'pending',
|
||||
'REQUEST': 'pending',
|
||||
'DMOVE': 'pending',
|
||||
'CANCEL': 'failed',
|
||||
'DONE': 'ok',
|
||||
};
|
||||
return this.safeString (statuses, status, status);
|
||||
}
|
||||
|
||||
parseTransactionType (type) {
|
||||
const types = {
|
||||
'd': 'deposit',
|
||||
'w': 'withdrawal',
|
||||
};
|
||||
return this.safeString (types, type, type);
|
||||
}
|
||||
|
||||
handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
return;
|
||||
}
|
||||
const resultString = this.safeString (response, 'result', '');
|
||||
if (resultString !== 'false') {
|
||||
return;
|
||||
}
|
||||
const errorCode = this.safeString (response, 'code');
|
||||
const message = this.safeString (response, 'message', body);
|
||||
if (errorCode !== undefined) {
|
||||
const feedback = this.safeString (this.exceptions['errorCodeNames'], errorCode, message);
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback);
|
||||
}
|
||||
}
|
||||
};
|
||||
+707
@@ -0,0 +1,707 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Exchange = require ('./base/Exchange');
|
||||
const { ExchangeError, ArgumentsRequired, BadRequest, OrderNotFound, InvalidOrder, InvalidNonce, InsufficientFunds, AuthenticationError, PermissionDenied, NotSupported, OnMaintenance, RateLimitExceeded } = require ('./base/errors');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class gemini extends Exchange {
|
||||
describe () {
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'gemini',
|
||||
'name': 'Gemini',
|
||||
'countries': [ 'US' ],
|
||||
'rateLimit': 1500, // 200 for private API
|
||||
'version': 'v1',
|
||||
'has': {
|
||||
'fetchDepositAddress': false,
|
||||
'createDepositAddress': true,
|
||||
'CORS': false,
|
||||
'fetchBidsAsks': false,
|
||||
'fetchTickers': false,
|
||||
'fetchMyTrades': true,
|
||||
'fetchOrder': true,
|
||||
'fetchOrders': false,
|
||||
'fetchOpenOrders': true,
|
||||
'fetchClosedOrders': false,
|
||||
'createMarketOrder': false,
|
||||
'withdraw': true,
|
||||
'fetchTransactions': true,
|
||||
'fetchWithdrawals': false,
|
||||
'fetchDeposits': false,
|
||||
'fetchOHLCV': true,
|
||||
},
|
||||
'urls': {
|
||||
'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg',
|
||||
'api': {
|
||||
'public': 'https://api.gemini.com',
|
||||
'private': 'https://api.gemini.com',
|
||||
'web': 'https://docs.gemini.com',
|
||||
},
|
||||
'www': 'https://gemini.com/',
|
||||
'doc': [
|
||||
'https://docs.gemini.com/rest-api',
|
||||
'https://docs.sandbox.gemini.com',
|
||||
],
|
||||
'test': {
|
||||
'public': 'https://api.sandbox.gemini.com',
|
||||
'private': 'https://api.sandbox.gemini.com',
|
||||
'web': 'https://docs.sandbox.gemini.com',
|
||||
},
|
||||
'fees': [
|
||||
'https://gemini.com/api-fee-schedule',
|
||||
'https://gemini.com/trading-fees',
|
||||
'https://gemini.com/transfer-fees',
|
||||
],
|
||||
},
|
||||
'api': {
|
||||
'web': {
|
||||
'get': [
|
||||
'rest-api',
|
||||
],
|
||||
},
|
||||
'public': {
|
||||
'get': [
|
||||
'v1/symbols',
|
||||
'v1/pubticker/{symbol}',
|
||||
'v1/book/{symbol}',
|
||||
'v1/trades/{symbol}',
|
||||
'v1/auction/{symbol}',
|
||||
'v1/auction/{symbol}/history',
|
||||
'v2/candles/{symbol}/{timeframe}',
|
||||
'v2/ticker/{symbol}',
|
||||
],
|
||||
},
|
||||
'private': {
|
||||
'post': [
|
||||
'v1/order/new',
|
||||
'v1/order/cancel',
|
||||
'v1/order/cancel/session',
|
||||
'v1/order/cancel/all',
|
||||
'v1/order/status',
|
||||
'v1/orders',
|
||||
'v1/mytrades',
|
||||
'v1/notionalvolume',
|
||||
'v1/tradevolume',
|
||||
'v1/transfers',
|
||||
'v1/balances',
|
||||
'v1/deposit/{currency}/newAddress',
|
||||
'v1/withdraw/{currency}',
|
||||
'v1/heartbeat',
|
||||
'v1/transfers',
|
||||
],
|
||||
},
|
||||
},
|
||||
'fees': {
|
||||
'trading': {
|
||||
'taker': 0.0035,
|
||||
'maker': 0.001,
|
||||
},
|
||||
},
|
||||
'httpExceptions': {
|
||||
'400': BadRequest, // Auction not open or paused, ineligible timing, market not open, or the request was malformed, in the case of a private API request, missing or malformed Gemini private API authentication headers
|
||||
'403': PermissionDenied, // The API key is missing the role necessary to access this private API endpoint
|
||||
'404': OrderNotFound, // Unknown API entry point or Order not found
|
||||
'406': InsufficientFunds, // Insufficient Funds
|
||||
'429': RateLimitExceeded, // Rate Limiting was applied
|
||||
'500': ExchangeError, // The server encountered an error
|
||||
'502': ExchangeError, // Technical issues are preventing the request from being satisfied
|
||||
'503': OnMaintenance, // The exchange is down for maintenance
|
||||
},
|
||||
'timeframes': {
|
||||
'1m': '1m',
|
||||
'5m': '5m',
|
||||
'15m': '15m',
|
||||
'30m': '30m',
|
||||
'1h': '1hr',
|
||||
'6h': '6hr',
|
||||
'1d': '1day',
|
||||
},
|
||||
'exceptions': {
|
||||
'exact': {
|
||||
'AuctionNotOpen': BadRequest, // Failed to place an auction-only order because there is no current auction open for this symbol
|
||||
'ClientOrderIdTooLong': BadRequest, // The Client Order ID must be under 100 characters
|
||||
'ClientOrderIdMustBeString': BadRequest, // The Client Order ID must be a string
|
||||
'ConflictingOptions': BadRequest, // New orders using a combination of order execution options are not supported
|
||||
'EndpointMismatch': BadRequest, // The request was submitted to an endpoint different than the one in the payload
|
||||
'EndpointNotFound': BadRequest, // No endpoint was specified
|
||||
'IneligibleTiming': BadRequest, // Failed to place an auction order for the current auction on this symbol because the timing is not eligible, new orders may only be placed before the auction begins.
|
||||
'InsufficientFunds': InsufficientFunds, // The order was rejected because of insufficient funds
|
||||
'InvalidJson': BadRequest, // The JSON provided is invalid
|
||||
'InvalidNonce': InvalidNonce, // The nonce was not greater than the previously used nonce, or was not present
|
||||
'InvalidOrderType': InvalidOrder, // An unknown order type was provided
|
||||
'InvalidPrice': InvalidOrder, // For new orders, the price was invalid
|
||||
'InvalidQuantity': InvalidOrder, // A negative or otherwise invalid quantity was specified
|
||||
'InvalidSide': InvalidOrder, // For new orders, and invalid side was specified
|
||||
'InvalidSignature': AuthenticationError, // The signature did not match the expected signature
|
||||
'InvalidSymbol': BadRequest, // An invalid symbol was specified
|
||||
'InvalidTimestampInPayload': BadRequest, // The JSON payload contained a timestamp parameter with an unsupported value.
|
||||
'Maintenance': OnMaintenance, // The system is down for maintenance
|
||||
'MarketNotOpen': InvalidOrder, // The order was rejected because the market is not accepting new orders
|
||||
'MissingApikeyHeader': AuthenticationError, // The X-GEMINI-APIKEY header was missing
|
||||
'MissingOrderField': InvalidOrder, // A required order_id field was not specified
|
||||
'MissingRole': AuthenticationError, // The API key used to access this endpoint does not have the required role assigned to it
|
||||
'MissingPayloadHeader': AuthenticationError, // The X-GEMINI-PAYLOAD header was missing
|
||||
'MissingSignatureHeader': AuthenticationError, // The X-GEMINI-SIGNATURE header was missing
|
||||
'NoSSL': AuthenticationError, // You must use HTTPS to access the API
|
||||
'OptionsMustBeArray': BadRequest, // The options parameter must be an array.
|
||||
'OrderNotFound': OrderNotFound, // The order specified was not found
|
||||
'RateLimit': RateLimitExceeded, // Requests were made too frequently. See Rate Limits below.
|
||||
'System': ExchangeError, // We are experiencing technical issues
|
||||
'UnsupportedOption': BadRequest, // This order execution option is not supported.
|
||||
},
|
||||
'broad': {
|
||||
'The Gemini Exchange is currently undergoing maintenance.': OnMaintenance, // The Gemini Exchange is currently undergoing maintenance. Please check https://status.gemini.com/ for more information.
|
||||
},
|
||||
},
|
||||
'options': {
|
||||
'fetchMarketsMethod': 'fetch_markets_from_web',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMarkets (params = {}) {
|
||||
const method = this.safeValue (this.options, 'fetchMarketsMethod', 'fetch_markets_from_api');
|
||||
return await this[method] (params);
|
||||
}
|
||||
|
||||
async fetchMarketsFromWeb (symbols = undefined, params = {}) {
|
||||
const response = await this.webGetRestApi (params);
|
||||
const sections = response.split ('<h1 id="symbols-and-minimums">Symbols and minimums</h1>');
|
||||
const numSections = sections.length;
|
||||
const error = this.id + ' the ' + this.name + ' API doc HTML markup has changed, breaking the parser of order limits and precision info for ' + this.name + ' markets.';
|
||||
if (numSections !== 2) {
|
||||
throw new NotSupported (error);
|
||||
}
|
||||
const tables = sections[1].split ('tbody>');
|
||||
const numTables = tables.length;
|
||||
if (numTables < 2) {
|
||||
throw new NotSupported (error);
|
||||
}
|
||||
const rows = tables[1].split ("\n<tr>\n"); // eslint-disable-line quotes
|
||||
const numRows = rows.length;
|
||||
if (numRows < 2) {
|
||||
throw new NotSupported (error);
|
||||
}
|
||||
const result = [];
|
||||
// skip the first element (empty string)
|
||||
for (let i = 1; i < numRows; i++) {
|
||||
const row = rows[i];
|
||||
const cells = row.split ("</td>\n"); // eslint-disable-line quotes
|
||||
const numCells = cells.length;
|
||||
if (numCells < 9) {
|
||||
throw new NotSupported (error);
|
||||
}
|
||||
// [
|
||||
// '<td>BTC', // currency
|
||||
// '<td>0.00001 BTC (1e-5)', // min order size
|
||||
// '<td>0.00000001 BTC (1e-8)', // tick size
|
||||
// '<td>0.01 USD', // usd price increment
|
||||
// '<td>N/A', // btc price increment
|
||||
// '<td>0.0001 ETH (1e-4)', // eth price increment
|
||||
// '<td>0.0001 BCH (1e-4)', // bch price increment
|
||||
// '<td>0.001 LTC (1e-3)', // ltc price increment
|
||||
// '</tr>'
|
||||
// ]
|
||||
//
|
||||
const uppercaseBaseId = cells[0].replace ('<td>', '');
|
||||
const baseId = uppercaseBaseId.toLowerCase ();
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quoteIds = [ 'usd', 'btc', 'eth', 'bch', 'ltc' ];
|
||||
const minAmountString = cells[1].replace ('<td>', '');
|
||||
const minAmountParts = minAmountString.split (' ');
|
||||
const minAmount = this.safeFloat (minAmountParts, 0);
|
||||
const amountPrecisionString = cells[2].replace ('<td>', '');
|
||||
const amountPrecisionParts = amountPrecisionString.split (' ');
|
||||
const amountPrecision = this.precisionFromString (amountPrecisionParts[0]);
|
||||
for (let j = 0; j < quoteIds.length; j++) {
|
||||
const quoteId = quoteIds[j];
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const pricePrecisionIndex = this.sum (3, j);
|
||||
const pricePrecisionString = cells[pricePrecisionIndex].replace ('<td>', '');
|
||||
if (pricePrecisionString === 'N/A') {
|
||||
continue;
|
||||
}
|
||||
const pricePrecisionParts = pricePrecisionString.split (' ');
|
||||
const pricePrecision = this.precisionFromString (pricePrecisionParts[0]);
|
||||
const symbol = base + '/' + quote;
|
||||
const id = baseId + quoteId;
|
||||
const active = undefined;
|
||||
result.push ({
|
||||
'id': id,
|
||||
'info': row,
|
||||
'symbol': symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'baseId': baseId,
|
||||
'quoteId': quoteId,
|
||||
'active': active,
|
||||
'precision': {
|
||||
'amount': amountPrecision,
|
||||
'price': pricePrecision,
|
||||
},
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': minAmount,
|
||||
'max': undefined,
|
||||
},
|
||||
'price': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
'cost': {
|
||||
'min': undefined,
|
||||
'max': undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchMarketsFromAPI (params = {}) {
|
||||
const response = await this.publicGetV1Symbols (params);
|
||||
const result = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const id = response[i];
|
||||
const market = id;
|
||||
const baseId = id.slice (0, 3);
|
||||
const quoteId = id.slice (3, 6);
|
||||
const base = this.safeCurrencyCode (baseId);
|
||||
const quote = this.safeCurrencyCode (quoteId);
|
||||
const symbol = base + '/' + quote;
|
||||
const precision = {
|
||||
'amount': undefined,
|
||||
'price': undefined,
|
||||
};
|
||||
result.push ({
|
||||
'id': id,
|
||||
'info': market,
|
||||
'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,
|
||||
},
|
||||
},
|
||||
'active': undefined,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetchOrderBook (symbol, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'symbol': this.marketId (symbol),
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit_bids'] = limit;
|
||||
request['limit_asks'] = limit;
|
||||
}
|
||||
const response = await this.publicGetV1BookSymbol (this.extend (request, params));
|
||||
return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount');
|
||||
}
|
||||
|
||||
async fetchTicker (symbol, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const ticker = await this.publicGetV1PubtickerSymbol (this.extend (request, params));
|
||||
const timestamp = this.safeInteger (ticker['volume'], 'timestamp');
|
||||
const baseCurrency = market['base']; // unified structures are guaranteed to have unified fields
|
||||
const quoteCurrency = market['quote']; // so we don't need safe-methods for unified structures
|
||||
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': this.safeFloat (ticker['volume'], baseCurrency),
|
||||
'quoteVolume': this.safeFloat (ticker['volume'], quoteCurrency),
|
||||
'info': ticker,
|
||||
};
|
||||
}
|
||||
|
||||
parseTrade (trade, market = undefined) {
|
||||
const timestamp = this.safeInteger (trade, 'timestampms');
|
||||
const id = this.safeString (trade, 'tid');
|
||||
const orderId = this.safeString (trade, 'order_id');
|
||||
const feeCurrencyId = this.safeString (trade, 'fee_currency');
|
||||
const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId);
|
||||
const fee = {
|
||||
'cost': this.safeFloat (trade, 'fee_amount'),
|
||||
'currency': feeCurrencyCode,
|
||||
};
|
||||
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 type = undefined;
|
||||
const side = this.safeStringLower (trade, 'type');
|
||||
let symbol = undefined;
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
return {
|
||||
'id': id,
|
||||
'order': orderId,
|
||||
'info': trade,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'takerOrMaker': undefined,
|
||||
'price': price,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
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.publicGetV1TradesSymbol (this.extend (request, params));
|
||||
return this.parseTrades (response, market, since, limit);
|
||||
}
|
||||
|
||||
async fetchBalance (params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostV1Balances (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['free'] = this.safeFloat (balance, 'available');
|
||||
account['total'] = this.safeFloat (balance, 'amount');
|
||||
result[code] = account;
|
||||
}
|
||||
return this.parseBalance (result);
|
||||
}
|
||||
|
||||
parseOrder (order, market = undefined) {
|
||||
const timestamp = this.safeInteger (order, 'timestampms');
|
||||
const amount = this.safeFloat (order, 'original_amount');
|
||||
const remaining = this.safeFloat (order, 'remaining_amount');
|
||||
const filled = this.safeFloat (order, 'executed_amount');
|
||||
let status = 'closed';
|
||||
if (order['is_live']) {
|
||||
status = 'open';
|
||||
}
|
||||
if (order['is_cancelled']) {
|
||||
status = 'canceled';
|
||||
}
|
||||
const price = this.safeFloat (order, 'price');
|
||||
const average = this.safeFloat (order, 'avg_execution_price');
|
||||
let cost = undefined;
|
||||
if (filled !== undefined) {
|
||||
if (average !== undefined) {
|
||||
cost = filled * average;
|
||||
}
|
||||
}
|
||||
let type = this.safeString (order, 'type');
|
||||
if (type === 'exchange limit') {
|
||||
type = 'limit';
|
||||
} else if (type === 'market buy' || type === 'market sell') {
|
||||
type = 'market';
|
||||
} else {
|
||||
type = order['type'];
|
||||
}
|
||||
const fee = undefined;
|
||||
let symbol = undefined;
|
||||
if (market === undefined) {
|
||||
const marketId = this.safeString (order, 'symbol');
|
||||
if (marketId in this.markets_by_id) {
|
||||
market = this.markets_by_id[marketId];
|
||||
}
|
||||
}
|
||||
if (market !== undefined) {
|
||||
symbol = market['symbol'];
|
||||
}
|
||||
const id = this.safeString (order, 'order_id');
|
||||
const side = this.safeStringLower (order, 'side');
|
||||
const clientOrderId = this.safeString (order, 'client_order_id');
|
||||
return {
|
||||
'id': id,
|
||||
'clientOrderId': clientOrderId,
|
||||
'info': order,
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'lastTradeTimestamp': undefined,
|
||||
'status': status,
|
||||
'symbol': symbol,
|
||||
'type': type,
|
||||
'side': side,
|
||||
'price': price,
|
||||
'average': average,
|
||||
'cost': cost,
|
||||
'amount': amount,
|
||||
'filled': filled,
|
||||
'remaining': remaining,
|
||||
'fee': fee,
|
||||
'trades': undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
const response = await this.privatePostV1OrderStatus (this.extend (request, params));
|
||||
return this.parseOrder (response);
|
||||
}
|
||||
|
||||
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const response = await this.privatePostV1Orders (params);
|
||||
let orders = this.parseOrders (response, undefined, since, limit);
|
||||
if (symbol !== undefined) {
|
||||
const market = this.market (symbol); // throws on non-existent symbol
|
||||
orders = this.filterBySymbol (orders, market['symbol']);
|
||||
}
|
||||
return orders;
|
||||
}
|
||||
|
||||
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 nonce = this.nonce ();
|
||||
const request = {
|
||||
'client_order_id': nonce.toString (),
|
||||
'symbol': this.marketId (symbol),
|
||||
'amount': amount.toString (),
|
||||
'price': price.toString (),
|
||||
'side': side,
|
||||
'type': 'exchange limit', // gemini allows limit orders only
|
||||
};
|
||||
const response = await this.privatePostV1OrderNew (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': response['order_id'],
|
||||
};
|
||||
}
|
||||
|
||||
async cancelOrder (id, symbol = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {
|
||||
'order_id': id,
|
||||
};
|
||||
return await this.privatePostV1OrderCancel (this.extend (request, params));
|
||||
}
|
||||
|
||||
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 = {
|
||||
'symbol': market['id'],
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
request['limit_trades'] = limit;
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['timestamp'] = parseInt (since / 1000);
|
||||
}
|
||||
const response = await this.privatePostV1Mytrades (this.extend (request, params));
|
||||
return this.parseTrades (response, market, 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,
|
||||
'address': address,
|
||||
};
|
||||
const response = await this.privatePostV1WithdrawCurrency (this.extend (request, params));
|
||||
return {
|
||||
'info': response,
|
||||
'id': this.safeString (response, 'txHash'),
|
||||
};
|
||||
}
|
||||
|
||||
nonce () {
|
||||
return this.milliseconds ();
|
||||
}
|
||||
|
||||
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const request = {};
|
||||
if (limit !== undefined) {
|
||||
request['limit_transfers'] = limit;
|
||||
}
|
||||
if (since !== undefined) {
|
||||
request['timestamp'] = since;
|
||||
}
|
||||
const response = await this.privatePostV1Transfers (this.extend (request, params));
|
||||
return this.parseTransactions (response);
|
||||
}
|
||||
|
||||
parseTransaction (transaction, currency = undefined) {
|
||||
const timestamp = this.safeInteger (transaction, 'timestampms');
|
||||
const currencyId = this.safeString (transaction, 'currency');
|
||||
const code = this.safeCurrencyCode (currencyId, currency);
|
||||
const address = this.safeString (transaction, 'destination');
|
||||
const type = this.safeStringLower (transaction, 'type');
|
||||
let status = 'pending';
|
||||
// When deposits show as Advanced or Complete they are available for trading.
|
||||
if (transaction['status']) {
|
||||
status = 'ok';
|
||||
}
|
||||
let fee = undefined;
|
||||
const feeAmount = this.safeFloat (transaction, 'feeAmount');
|
||||
if (feeAmount !== undefined) {
|
||||
fee = {
|
||||
'cost': feeAmount,
|
||||
'currency': code,
|
||||
};
|
||||
}
|
||||
return {
|
||||
'info': transaction,
|
||||
'id': this.safeString (transaction, 'eid'),
|
||||
'txid': this.safeString (transaction, 'txHash'),
|
||||
'timestamp': timestamp,
|
||||
'datetime': this.iso8601 (timestamp),
|
||||
'address': address,
|
||||
'tag': undefined, // or is it defined?
|
||||
'type': type, // direction of the transaction, ('deposit' | 'withdraw')
|
||||
'amount': this.safeFloat (transaction, 'amount'),
|
||||
'currency': code,
|
||||
'status': status,
|
||||
'updated': undefined,
|
||||
'fee': fee,
|
||||
};
|
||||
}
|
||||
|
||||
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
|
||||
let url = '/' + this.implodeParams (path, params);
|
||||
const query = this.omit (params, this.extractParams (path));
|
||||
if (api === 'private') {
|
||||
this.checkRequiredCredentials ();
|
||||
const nonce = this.nonce ();
|
||||
const request = this.extend ({
|
||||
'request': url,
|
||||
'nonce': nonce,
|
||||
}, query);
|
||||
let payload = this.json (request);
|
||||
payload = this.stringToBase64 (this.encode (payload));
|
||||
const signature = this.hmac (payload, this.encode (this.secret), 'sha384');
|
||||
headers = {
|
||||
'Content-Type': 'text/plain',
|
||||
'X-GEMINI-APIKEY': this.apiKey,
|
||||
'X-GEMINI-PAYLOAD': this.decode (payload),
|
||||
'X-GEMINI-SIGNATURE': signature,
|
||||
};
|
||||
} else {
|
||||
if (Object.keys (query).length) {
|
||||
url += '?' + this.urlencode (query);
|
||||
}
|
||||
}
|
||||
url = this.urls['api'][api] + url;
|
||||
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
|
||||
}
|
||||
|
||||
handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
|
||||
if (response === undefined) {
|
||||
if (typeof body === 'string') {
|
||||
const feedback = this.id + ' ' + body;
|
||||
this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback);
|
||||
}
|
||||
return; // fallback to default error handler
|
||||
}
|
||||
//
|
||||
// {
|
||||
// "result": "error",
|
||||
// "reason": "BadNonce",
|
||||
// "message": "Out-of-sequence nonce <1234> precedes previously used nonce <2345>"
|
||||
// }
|
||||
//
|
||||
const result = this.safeString (response, 'result');
|
||||
if (result === 'error') {
|
||||
const reason = this.safeString (response, 'reason');
|
||||
const message = this.safeString (response, 'message');
|
||||
const feedback = this.id + ' ' + message;
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], reason, feedback);
|
||||
this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback);
|
||||
this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback);
|
||||
throw new ExchangeError (feedback); // unknown message
|
||||
}
|
||||
}
|
||||
|
||||
async createDepositAddress (code, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const currency = this.currency (code);
|
||||
const request = {
|
||||
'currency': currency['id'],
|
||||
};
|
||||
const response = await this.privatePostV1DepositCurrencyNewAddress (this.extend (request, params));
|
||||
const address = this.safeString (response, 'address');
|
||||
this.checkAddress (address);
|
||||
return {
|
||||
'currency': code,
|
||||
'address': address,
|
||||
'tag': undefined,
|
||||
'info': response,
|
||||
};
|
||||
}
|
||||
|
||||
async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) {
|
||||
await this.loadMarkets ();
|
||||
const market = this.market (symbol);
|
||||
const request = {
|
||||
'timeframe': this.timeframes[timeframe],
|
||||
'symbol': market['id'],
|
||||
};
|
||||
const response = await this.publicGetV2CandlesSymbolTimeframe (this.extend (request, params));
|
||||
return this.parseOHLCVs (response, market, timeframe, since, limit);
|
||||
}
|
||||
};
|
||||
+1526
File diff suppressed because it is too large.
Load diff
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const hitbtc = require ('./hitbtc');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
module.exports = class hitbtc2 extends hitbtc {
|
||||
describe () {
|
||||
// this is a temporary stub for backward compatibility
|
||||
// https://github.com/ccxt/ccxt/issues/6678
|
||||
return this.deepExtend (super.describe (), {
|
||||
'id': 'hitbtc2',
|
||||
});
|
||||
}
|
||||
};
|
||||
Loaded 100 of 3408 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user