Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/ccxt/ccxt
Browse files Browse the repository at this point in the history
  • Loading branch information
frosty00 committed Jun 6, 2018
2 parents 5aa058e + d7a312b commit 68e4325
Show file tree
Hide file tree
Showing 57 changed files with 1,019 additions and 286 deletions.
275 changes: 211 additions & 64 deletions build/ccxt.browser.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ccxt.js
Expand Up @@ -37,7 +37,7 @@ const Exchange = require ('./js/base/Exchange')
//-----------------------------------------------------------------------------
// this is updated by vss.js when building

const version = '1.14.123'
const version = '1.14.141'

Exchange.ccxtVersion = version

Expand Down
16 changes: 8 additions & 8 deletions doc/install.rst
Expand Up @@ -96,15 +96,8 @@ Proxy

In some specific cases you may want a proxy, if you experience issues with `DDoS protection by Cloudflare <https://github.com/ccxt/ccxt/wiki/Manual#ddos-protection-by-cloudflare>`__ or your network / country / IP is rejected by their filters.

If you need a proxy, use the ``proxy`` property (a string literal) containing base URL of http(s) proxy. It is for use with web browsers and from blocked locations.

**Bear in mind that each added intermediary contributes to the overall latency and roundtrip time. Longer delays can result in price slippage.**

The absolute exchange endpoint URL is appended to ``proxy`` string before HTTP request is sent to exchange. The proxy setting is an empty string ``''`` by default. Below are examples of a non-empty proxy string (last slash is mandatory!):

- ``kraken.proxy = 'https://crossorigin.me/'``
- ``gdax.proxy = 'https://cors-anywhere.herokuapp.com/'``

Python Proxies
~~~~~~~~~~~~~~

Expand Down Expand Up @@ -244,9 +237,16 @@ A more detailed documentation on using proxies with the sync python version of t
CORS (Access-Control-Allow-Origin)
----------------------------------

If you need a CORS proxy, use the ``proxy`` property (a string literal) containing base URL of http(s) proxy. It is for use with web browsers and from blocked locations.

CORS is `Cross-Origin Resource Sharing <https://en.wikipedia.org/wiki/Cross-origin_resource_sharing>`__. When accessing the HTTP REST API of an exchange from browser with ccxt library you may get a warning or an exception, saying ``No 'Access-Control-Allow-Origin' header is present on the requested resource``. That means that the exchange admins haven't enabled access to their API from arbitrary web browser pages.

You can still use the ccxt library from your browser via a CORS-proxy, which is very easy to set up or install. There are also public CORS proxies on the internet, like https://crossorigin.me.
You can still use the ccxt library from your browser via a CORS-proxy, which is very easy to set up or install. There are also public CORS proxies on the internet.

The absolute exchange endpoint URL is appended to ``proxy`` string before HTTP request is sent to exchange. The ``proxy`` setting is an empty string ``''`` by default. Below are examples of a non-empty ``proxy`` string (last slash is mandatory!):

- ``kraken.proxy = 'https://crossorigin.me/'``
- ``gdax.proxy = 'https://cors-anywhere.herokuapp.com/'``

To run your own CORS proxy locally you can either set up one of the existing ones or make a quick script of your own, like shown below.

Expand Down
22 changes: 21 additions & 1 deletion examples/js/cli.js
Expand Up @@ -2,11 +2,12 @@

//-----------------------------------------------------------------------------

const [processPath, , exchangeId, methodName, ... params] = process.argv.filter (x => !x.startsWith ('--'))
let [processPath, , exchangeId, methodName, ... params] = process.argv.filter (x => !x.startsWith ('--'))
, verbose = process.argv.includes ('--verbose')
, cloudscrape = process.argv.includes ('--cloudscrape')
, cfscrape = process.argv.includes ('--cfscrape')
, poll = process.argv.includes ('--poll')
, no_send = process.argv.includes ('--no-send')
, loadMarkets = process.argv.includes ('--load-markets')
, no_details = process.argv.includes ('--no-details')
, no_table = process.argv.includes ('--no-table')
Expand Down Expand Up @@ -123,6 +124,7 @@ let printSupportedExchanges = function () {
log ('--cloudscrape Use https://github.com/codemanki/cloudscraper to bypass Cloudflare')
log ('--cfscrape Use https://github.com/Anorov/cloudflare-scrape to bypass Cloudflare (requires python and cfscrape)')
log ('--poll Repeat continuously in rate-limited mode')
log ("--no-send Print the request but don't actually send it to the exchange (sets verbose and load-markets)")
log ('--load-markets Pre-load markets (for debugging)')
log ('--no-details Do not print detailed fetch responses')
log ('--no-table Do not print tabulated fetch responses')
Expand Down Expand Up @@ -210,9 +212,27 @@ async function main () {
if (cfscrape)
exchange.headers = cfscrapeCookies (www)

loadMarkets = no_send ? true : loadMarkets

if (loadMarkets)
await exchange.loadMarkets ()

if (no_send) {

exchange.verbose = no_send
exchange.fetch = function fetch (url, method = 'GET', headers = undefined, body = undefined) {
log.dim.noLocate ('-------------------------------------------')
log.dim.noLocate (exchange.iso8601 (exchange.milliseconds ()))
log.green.unlimited ({
url,
method,
headers,
body,
})
process.exit ()
}
}

if (typeof exchange[methodName] === 'function') {

log (exchange.id + '.' + methodName, '(' + args.join (', ') + ')')
Expand Down
29 changes: 29 additions & 0 deletions examples/js/market-status-and-currency-status.js
@@ -0,0 +1,29 @@
'use strict'

const ccxt = require ('../../ccxt')
, log = require ('ololog')
, asTable = require ('as-table')

;(async function main () {

let kraken = new ccxt.kraken ({ enableRateLimit: true })
await kraken.loadMarkets ()

const markets = Object.values (kraken.markets).map (market => ({
symbol: market.symbol,
active: market.active,
}))

log.bright.green.noLocate ('Markets:')
log.green.noLocate (asTable (markets), '\n')

const currencies = Object.values (kraken.currencies).map (currency => ({
code: currency.code,
active: currency.active,
status: currency.status,
}))

log.bright.yellow.noLocate ('Currencies:')
log.yellow.noLocate (asTable (currencies))

}) ()
32 changes: 32 additions & 0 deletions examples/py/normalize-sparse-candle-timestamps.py
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-

import os
import sys
from pprint import pprint

root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(root + '/python')

import ccxt # noqa: E402

exchange = ccxt.cryptopia({'enableRateLimit': True})

symbol = 'ETH/BTC'
timeframe = '1h'

candles = exchange.fetch_ohlcv(symbol, timeframe)

# timeframe duration in seconds
duration = exchange.parse_timeframe(timeframe)

# timeframe duration in milliseconds
duration *= 1000

pprint([[
exchange.iso8601(int(round(candle[0] / duration)) * duration),
candle[1], # o
candle[2], # h
candle[3], # l
candle[4], # c
candle[5] # v
] for candle in candles])
81 changes: 67 additions & 14 deletions js/base/Exchange.js
Expand Up @@ -182,15 +182,68 @@ module.exports = class Exchange {
this.proxy = ''
this.origin = '*' // CORS origin

this.iso8601 = timestamp => ((typeof timestamp === 'undefined') ? timestamp : new Date (timestamp).toISOString ())
this.parse8601 = x => Date.parse ((((x.indexOf ('+') >= 0) || (x.slice (-1) === 'Z')) ? x : (x + 'Z').replace (/\s(\d\d):/, 'T$1:')))
this.parseDate = (x) => {
if (typeof x === 'undefined')
return x
return ((x.indexOf ('GMT') >= 0) ?
Date.parse (x) :
this.parse8601 (x))
this.iso8601 = (timestamp) => {
const _timestampNumber = parseInt (timestamp, 10);

// undefined, null and lots of nasty non-numeric values yield NaN
if (isNaN (_timestampNumber) || _timestampNumber < 0) {
return undefined;
}

if (_timestampNumber < 0) {
return undefined;
}

// last line of defence
try {
return new Date (_timestampNumber).toISOString ();
} catch (e) {
return undefined;
}
}

this.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 (isNaN (candidate)) {
return undefined;
}
return candidate;
} catch (e) {
return undefined;
}
}

this.parseDate = (x) => {
if (typeof x !== 'string' || !x) {
return undefined;
}

if (x.indexOf ('GMT') >= 0) {
try {
return Date.parse (x);
} catch (e) {
return undefined;
}
}

return this.parse8601 (x);
}

this.microseconds = () => now () * 1000 // TODO: utilize performance.now for that purpose
this.seconds = () => Math.floor (now () / 1000)

Expand Down Expand Up @@ -903,25 +956,25 @@ module.exports = class Exchange {
}

filterBySinceLimit (array, since = undefined, limit = undefined) {
if (typeof since !== 'undefined')
if (typeof since !== 'undefined' && since !== null)
array = array.filter (entry => entry.timestamp >= since)
if (typeof limit !== 'undefined')
if (typeof limit !== 'undefined' && limit !== null)
array = array.slice (0, limit)
return array
}

filterBySymbolSinceLimit (array, symbol = undefined, since = undefined, limit = undefined) {

const symbolIsDefined = typeof symbol !== 'undefined'
const sinceIsDefined = typeof since !== 'undefined'
const symbolIsDefined = typeof symbol !== 'undefined' && symbol !== null
const sinceIsDefined = typeof since !== 'undefined' && since !== null

// single-pass filter for both symbol and since
if (symbolIsDefined || sinceIsDefined)
array = Object.values (array).filter (entry =>
((symbolIsDefined ? (entry.symbol === symbol) : true) &&
(sinceIsDefined ? (entry.timestamp >= since) : true)))

if (typeof limit !== 'undefined')
if (typeof limit !== 'undefined' && limit !== null)
array = Object.values (array).slice (0, limit)

return array
Expand All @@ -932,7 +985,7 @@ module.exports = class Exchange {
objects = Object.values (objects)

// return all of them if no values were passed
if (typeof values === 'undefined')
if (typeof values === 'undefined' || values === null)
return indexed ? indexBy (objects, key) : objects

let result = []
Expand Down
3 changes: 2 additions & 1 deletion js/cobinhood.js
Expand Up @@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, InsufficientFunds, InvalidNonce } = require ('./base/errors');
const { ExchangeError, InsufficientFunds, InvalidNonce, PermissionDenied } = require ('./base/errors');

// ---------------------------------------------------------------------------

Expand Down Expand Up @@ -129,6 +129,7 @@ module.exports = class cobinhood extends Exchange {
'exceptions': {
'insufficient_balance': InsufficientFunds,
'invalid_nonce': InvalidNonce,
'unauthorized_scope': PermissionDenied,
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion js/coinmarketcap.js
Expand Up @@ -222,7 +222,7 @@ module.exports = class coinmarketcap extends Exchange {
let tickers = {};
for (let t = 0; t < response.length; t++) {
let ticker = response[t];
let currencyId = (currency in this.currencies) ? this.currencies[currency]['id'] : currency.toLowerCase ();
let currencyId = currency.toLowerCase ();
let id = ticker['id'] + '/' + currencyId;
let symbol = id;
let market = undefined;
Expand Down
22 changes: 19 additions & 3 deletions js/gateio.js
Expand Up @@ -113,6 +113,17 @@ module.exports = class gateio extends Exchange {
'20': 'Your order size is too small',
'21': 'You don\'t have enough fund',
},
'options': {
'limits': {
'cost': {
'min': {
'BTC': 0.0001,
'ETH': 0.001,
'USDT': 1,
},
},
},
},
});
}

Expand Down Expand Up @@ -145,8 +156,10 @@ module.exports = class gateio extends Exchange {
'min': Math.pow (10, -details['decimal_places']),
'max': undefined,
};
let defaultCost = amountLimits['min'] * priceLimits['min'];
let minCost = this.safeFloat (this.options['limits']['cost']['min'], quote, defaultCost);
let costLimits = {
'min': amountLimits['min'] * priceLimits['min'],
'min': minCost,
'max': undefined,
};
let limits = {
Expand Down Expand Up @@ -354,9 +367,12 @@ module.exports = class gateio extends Exchange {
}
if (typeof market !== 'undefined')
symbol = market['symbol'];
let datetime = undefined;
let timestamp = this.safeInteger (order, 'timestamp');
if (typeof timestamp !== 'undefined')
if (typeof timestamp !== 'undefined') {
timestamp *= 1000;
datetime = this.iso8601 (timestamp);
}
let status = this.safeString (order, 'status');
if (typeof status !== 'undefined')
status = this.parseOrderStatus (status);
Expand All @@ -378,7 +394,7 @@ module.exports = class gateio extends Exchange {
}
return {
'id': id,
'datetime': this.iso8601 (timestamp),
'datetime': datetime,
'timestamp': timestamp,
'status': status,
'symbol': symbol,
Expand Down
7 changes: 6 additions & 1 deletion js/kraken.js
Expand Up @@ -628,8 +628,13 @@ module.exports = class kraken extends Exchange {
'ordertype': type,
'volume': this.amountToPrecision (symbol, amount),
};
if (type === 'limit')
let priceIsDefined = (typeof price !== 'undefined');
let marketOrder = (type === 'market');
let limitOrder = (type === 'limit');
let shouldIncludePrice = limitOrder || (!marketOrder && priceIsDefined);
if (shouldIncludePrice) {
order['price'] = this.priceToPrecision (symbol, price);
}
let response = await this.privatePostAddOrder (this.extend (order, params));
let id = this.safeValue (response['result'], 'txid');
if (typeof id !== 'undefined') {
Expand Down
3 changes: 2 additions & 1 deletion js/kucoin.js
Expand Up @@ -345,7 +345,8 @@ module.exports = class kucoin extends Exchange {
};
} else {
orderbook = response['data'];
timestamp = response['data']['timestamp'];
timestamp = this.safeInteger (response, 'timestamp');
timestamp = this.safeInteger (response['data'], 'timestamp', timestamp);
}
return this.parseOrderBook (orderbook, timestamp, 'BUY', 'SELL');
}
Expand Down

0 comments on commit 68e4325

Please sign in to comment.