Skip to content

Commit

Permalink
Merge pull request #2 from aloysius-pgast/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
aloysius-pgast authored Sep 18, 2017
2 parents 6595fcb + 07c02f7 commit 18d713b
Show file tree
Hide file tree
Showing 26 changed files with 1,207 additions and 56 deletions.
51 changes: 40 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ Currently supports for following exchanges :
* [Poloniex](https://www.poloniex.com) ([**worst support**](https://www.reddit.com/r/PoloniexForum/) ever)
* More to come...

Following methods are currently supported :
Following API are currently supported :

* Retrieve pairs
* Retrieve tickers
* Retrieve order book
* Retrieve last executed trades
* List open orders
* List closed orders
* Retrieve balances

See [documentation](doc/exchanges/index.adoc) for an overview of each API
See [documentation in _doc_ directory](doc/exchanges/index.adoc) for an overview of each API

## Limitations

Expand All @@ -43,46 +44,46 @@ Margin trading is not supported (and is unlikely to be)

Currently supports following services :

* [CoinMarket](https://coinmarketcap.com/) (see [documentation](doc/coinmarketcap/index.adoc) for an overview of each API)
* [PushOver](https://pushover.net/) (see [documentation](doc/pushover/index.adoc) for an overview of each API)
* [CoinMarket](https://coinmarketcap.com/) (see [documentation in _doc_ directory](doc/coinmarketcap/index.adoc) for an overview of each API)
* [PushOver](https://pushover.net/) (see [documentation in _doc_ directory](doc/pushover/index.adoc) for an overview of each API)

## Rate limiting

Rate limiting is implemented when required by exchange thanks to [Bottleneck](https://www.npmjs.com/package/bottleneck)

## Installation

Install dependencies
* Install dependencies

```
npm install
```

Copy sample config
* Copy sample config

```
cp config/config.sample.json config/config.json
```

Check [documentation](doc/config.adoc) for detailed information on each config section
Check [documentation in _doc_ directory](doc/config.adoc) for detailed information on each config section

Start gateway
* Start gateway

```
node gateway.js
```

Check which exchanges are enabled
* Check which exchanges are enabled

Open http://127.0.0.1:8000/exchanges/ in your browser. You should see JSON content such as below :

```javascript
["binance","bittrex","poloniex"]
```

Check BTC & ETH prices on CoinMarketCap
* Check BTC & ETH prices on CoinMarketCap

open http://127.0.0.1:8000/coinmarketcap/tickers?symbols=BTC,ETH in your browser. You should see JSON content such as below :
Open http://127.0.0.1:8000/coinmarketcap/tickers?symbols=BTC,ETH in your browser. You should see JSON content such as below :

```javascript
[
Expand Down Expand Up @@ -127,6 +128,34 @@ open http://127.0.0.1:8000/coinmarketcap/tickers?symbols=BTC,ETH in your browser
]
```

* Place an order to buy 1 NEO at 0.0040BTC on Bittrex (assuming you have enough funds)

Execute the following in a terminal :

```
curl -X POST 'http://127.0.0.1:8000/exchanges/bittrex/openOrders?pair=BTC-NEO&quantity=1&targetRate=0.0040'
```

You should see JSON content such as below :

```
{"orderNumber":"8bc49a59-1056-4c20-90f2-893fff2be279"}
```

* Cancel above order (assuming order still exists)

Execute the following in a terminal :

```
curl -X DELETE 'http://127.0.0.1:8000/exchanges/bittrex/openOrders/8bc49a59-1056-4c20-90f2-893fff2be279'
```

You should see JSON content such as below in case order is valid :

```
{}
```

## Dependencies

This project was made possible thanks to following projects :
Expand Down
129 changes: 126 additions & 3 deletions app/exchanges/binance/exchange.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,6 @@ tickers(opt)
/**
* Returns existing pairs
*
* opt.useCache : if true cached version will be used (default = false)
*
* Result will be as below
*
* {
Expand All @@ -222,13 +220,19 @@ tickers(opt)
* },...
* }
*
* @param {boolean} opt.useCache : if true cached version will be used (optional, default = false)
* @param {string} opt.pair : retrieve a single pair (ex: BTC-ETH pair) (optional)
* @param {string} opt.currency : retrieve only pairs having a given currency (ex: ETH in BTC-ETH pair) (optional, will be ignored if pair is set)
* @param {string} opt.baseCurrency : retrieve only pairs having a given base currency (ex: BTC in BTC-ETH pair) (optional, will be ignored if currency or pair are set)
* @return {Promise}
*/
pairs(opt)
{
let self = this;
let timestamp = parseInt(new Date().getTime() / 1000.0);
if (undefined !== opt && undefined !== opt.useCache && opt.useCache && timestamp < this._cachedPairs.nextTimestamp)
let checkCurrencies = undefined !== opt && (undefined !== opt.pair || undefined !== opt.currency || undefined !== opt.baseCurrency);
// only use cache if currency/baseCurrency are not defined
if (!checkCurrencies && undefined !== opt && undefined !== opt.useCache && opt.useCache && timestamp < this._cachedPairs.nextTimestamp)
{
return new Promise((resolve, reject) => {
resolve(self._cachedPairs.cache);
Expand Down Expand Up @@ -267,6 +271,34 @@ pairs(opt)
currency = entry.symbol.substr(0, entry.symbol.length - 3);
}
let pair = baseCurrency + '-' + currency;
// check if we're interested in such pair / currency / base currency
if (checkCurrencies)
{
if (undefined !== opt.pair)
{
// ignore this pair
if (opt.pair != pair)
{
return;
}
}
else if (undefined !== opt.currency)
{
// ignore this pair
if (opt.currency != currency)
{
return;
}
}
else if (undefined !== opt.baseCurrency)
{
// ignore this pair
if (opt.baseCurrency != baseCurrency)
{
return;
}
}
}
list[pair] = {
pair:pair,
baseCurrency: baseCurrency,
Expand Down Expand Up @@ -358,6 +390,97 @@ pairs(opt)
});
}

/**
* Returns last trades
*
* If opt.outputFormat is 'exchange', the result returned by remote exchange will be returned untouched
*
* [
* {
* "a":1132434,
* "p":"0.07252000",
* "q":"0.50000000",
* "f":1199586,
* "l":1199586,
* "T":1505725537806,
* "m":true,
* "M":true
* },
* {
* "a":1132435,
* "p":"0.07252000",
* "q":"0.50000000",
* "f":1199587,
* "l":1199587,
* "T":1505725538108,
* "m":true,
* "M":true
* }
* ]
*
* If opt.outputFormat is 'custom', the result will be as below
*
* [
* {
* "id":1132933,
* "quantity":0.95,
* "rate":0.072699,
* "price":0.06906405,
* "timestamp":1505731777
* },
* {
* "id":1132932,
* "quantity":1,
* "rate":0.072602,
* "price":0.072602,
* "timestamp":1505731693
* }
* ]
*
* @param {string} opt.outputFormat (custom|exchange) if value is 'exchange' result returned by remote exchange will be returned untouched
* @param {integer} opt.afterTradeId only retrieve trade with an ID > opt.afterTradeId (optional, will be ignored if opt.outputFormat is set to 'exchange')
* @param {string} opt.pair pair to retrieve order book for (X-Y)
* @return {Promise} format depends on parameter opt.outputFormat
*/
trades(opt) {
let self = this;
return this._limiterGlobal.schedule(function(){
let exchangePair = self._toExchangePair(opt.pair);
let params = {symbol:exchangePair};
if (undefined !== opt.afterTradeId)
{
// fromId is inclusive, we want all trades with an ID > afterTradeId
params.fromId = opt.afterTradeId + 1;
}
let p = self._restClient.aggTrades(params);
// return raw results
if ('exchange' == opt.outputFormat)
{
return p;
}
return new Promise((resolve, reject) => {
p.then(function(data){
let list = [];
_.forEach(data, function(entry){
let quantity = parseFloat(entry.q);
let rate = parseFloat(entry.p);
let price = quantity * rate;
list.unshift({
id:entry.a,
quantity:quantity,
rate:rate,
price:price,
timestamp:parseInt(entry.T / 1000.0)
})
});
resolve(list);
}).catch(function(err){
reject(err.msg);
});
});
});
}

/**
* @param {string} orderNumber identifier of the order
* @param {string} pair pair of the order (X-Y)
Expand Down
68 changes: 65 additions & 3 deletions app/exchanges/binance/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ if (!config.exchanges.binance.enabled)
const ExchangeClass = require('./exchange');
const exchange = new ExchangeClass(config);

let getPairs = function(){
return exchange.pairs();
/**
* Retrieves existing pairs
* @param {string} currency : used to list pairs with a given currency (ex: ETH in BTC-ETH pair) (optional)
* @param {string} baseCurrency : used to list pairs with a given base currency (ex: BTC in BTC-ETH pair) (will be ignored if currency is set) (optional)
*/
let getPairs = function(opt){
return exchange.pairs(opt);
}
pairFinder.register('binance', getPairs);

Expand Down Expand Up @@ -96,9 +101,24 @@ app.get('/exchanges/binance/tickers/:pair', (req, res) => {

/**
* Retrieves existing pairs
* @param {string} pair : used to retrieve only a single pair (ex: BTC-ETH (optional)
* @param {string} currency : used to list pairs with a given currency (ex: ETH in BTC-ETH pair) (optional, will be ignored if pair is set)
* @param {string} baseCurrency : used to list pairs with a given base currency (ex: BTC in BTC-ETH pair) (optional, will be ignored if currency or pair are set)
*/
app.get('/exchanges/binance/pairs', (req, res) => {
let opt = {};
if (undefined !== req.query.pair && '' != req.query.pair)
{
opt.pair = req.query.pair;
}
else if (undefined != req.query.currency && '' != req.query.currency)
{
opt.currency = req.query.currency;
}
else if (undefined != req.query.baseCurrency && '' != req.query.baseCurrency)
{
opt.baseCurrency = req.query.baseCurrency;
}
exchange.pairs(opt)
.then(function(data) {
res.send(data);
Expand All @@ -121,7 +141,7 @@ app.get('/exchanges/binance/pairs', (req, res) => {
*
* @param {string} outputFormat (custom|exchange) if value is 'exchange' result returned by remote exchange will be returned untouched (optional, default = 'custom')
* @param {string} pair pair to retrieve order book for
* @param {integer} limit how many entries to retrieve (optional, default = 100, max = 100) (must be a )
* @param {integer} limit how many entries to retrieve (optional, default = 100, max = 100)
*/
app.get('/exchanges/binance/orderBooks/:pair', (req, res) => {
let opt = {outputFormat:'custom', limit:100};
Expand Down Expand Up @@ -162,6 +182,48 @@ app.get('/exchanges/binance/orderBooks/:pair', (req, res) => {
});
});

/**
* Returns last trades for a given pair (Binance only allows to retrieve last 500)
*
* @param {string} outputFormat (custom|exchange) if value is 'exchange' result returned by remote exchange will be returned untouched (optional, default = 'custom')
* @param {integer} afterTradeId only retrieve trade with an ID > afterTradeId (optional, will be ignored if outputFormat is set to 'exchange')
* @param {string} pair pair to retrieve last trades for
*/
app.get('/exchanges/binance/trades/:pair', (req, res) => {
let opt = {outputFormat:'custom'};
if (undefined === req.params.pair || '' == req.params.pair)
{
res.status(400).send({origin:"gateway",error:"Missing url parameter 'pair'"});
return;
}
opt.pair = req.params.pair;
if ('exchange' == req.query.outputFormat)
{
opt.outputFormat = 'exchange';
}
if ('custom' == opt.outputFormat)
{
if (undefined !== req.query.afterTradeId)
{
let afterTradeId = parseInt(req.query.afterTradeId);
if (isNaN(afterTradeId) || afterTradeId <= 0)
{
res.status(400).send({origin:"gateway",error:util.format("Parameter 'afterTradeId' should be an integer > 0 : value = '%s'", req.query.afterTradeId)});
return;
}
opt.afterTradeId = afterTradeId;
}
}
exchange.trades(opt)
.then(function(data) {
res.send(data);
})
.catch(function(err)
{
res.status(503).send({origin:"remote",error:err});
});
});

//-- below routes require valid key/secret
if ('' === config.exchanges.binance.key || '' === config.exchanges.binance.secret)
{
Expand Down
Loading

0 comments on commit 18d713b

Please sign in to comment.