forked from DeviaVir/zenbot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for QuadrigaCX API (DeviaVir#342)
* Docker compose update to use internal data volume. Makes it work with Docker for Windows * Initial exchange implemented Quadriga API public and private queries are working, testing the getQuote and getBalance. * JS Beautifier Config * Adjusting minimum and increments for quadriga products * Bug fixes and formatting * Fix bug in buy function (typo) prevent callback * Change formatting to follow the rest of zenbot * Fix markdown percentage option description * Fixed issues with buy command * Fix error handling in API calls * Prevent spamming calls to getOrder There is a general fall through to setTimer at the end of the function that must always run, the conditionals were causing additional uncessary checks which increases probability of hitting API rate limits. * Sell function tested and fixes * Fix formatting remove update-selectors script * Remove archives mapping from docker-compose.yml Not related to this chance, didn't serve a particularly usefull purpose * Update docker-compose.yml
- Loading branch information
Showing
9 changed files
with
350 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"indent_size": 2, | ||
"js": { | ||
"preserve-newlines": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
_ns: 'zenbot', | ||
_name: 'quadriga', | ||
|
||
'exchanges.quadriga': require('./exchange'), | ||
'exchanges.list[]': '#exchanges.quadriga' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
var QuadrigaCX = require('quadrigacx'), | ||
path = require('path'), | ||
colors = require('colors'), | ||
n = require('numbro') | ||
|
||
module.exports = function container(get, set, clear) { | ||
var c = get('conf') | ||
var shownWarnings = false | ||
|
||
var public_client, authed_client | ||
|
||
function publicClient() { | ||
if (!public_client) public_client = new QuadrigaCX("1", "", ""); | ||
return public_client | ||
} | ||
|
||
function authedClient() { | ||
if (!authed_client) { | ||
if (!c.quadriga || !c.quadriga.key || !c.quadriga.key === 'YOUR-API-KEY') { | ||
throw new Error('please configure your Quadriga credentials in ' + path.resolve(__dirname, 'conf.js')) | ||
} | ||
|
||
authed_client = new QuadrigaCX(c.quadriga.client_id, c.quadriga.key, c.quadriga.secret); | ||
} | ||
return authed_client | ||
} | ||
|
||
function joinProduct(product_id) { | ||
return (product_id.split('-')[0] + '_' + product_id.split('-')[1]).toLowerCase() | ||
} | ||
|
||
function retry(method, args, error) { | ||
if (error.code === 200) { | ||
console.error(('\QuadrigaCX API rate limit exceeded! unable to call ' + method + ', aborting').red) | ||
return; | ||
} | ||
|
||
if (method !== 'getTrades') { | ||
console.error(('\QuadrigaCX API is down! unable to call ' + method + ', retrying in 30s').red) | ||
} | ||
setTimeout(function() { | ||
exchange[method].apply(exchange, args) | ||
}, 30000) | ||
} | ||
|
||
var orders = {} | ||
|
||
var exchange = { | ||
name: 'quadriga', | ||
historyScan: 'backward', | ||
makerFee: 0.5, | ||
|
||
getProducts: function() { | ||
return require('./products.json') | ||
}, | ||
|
||
getTrades: function(opts, cb) { | ||
var func_args = [].slice.call(arguments) | ||
var args = { | ||
book: joinProduct(opts.product_id), | ||
time: 'hour' | ||
} | ||
|
||
var client = publicClient() | ||
client.api('transactions', args, function(err, trades) { | ||
if (!shownWarnings) { | ||
console.log('please note: the quadriga api does not support backfilling (trade/paper only).') | ||
console.log('please note: make sure to set the period to 1h') | ||
shownWarnings = true; | ||
} | ||
|
||
if (err) return retry('getTrades', func_args, err) | ||
if (trades.error) return retry('getTrades', func_args, trades.error) | ||
|
||
var trades = trades.map(function(trade) { | ||
return { | ||
trade_id: trade.tid, | ||
time: trade.date, | ||
size: trade.amount, | ||
price: trade.price, | ||
side: trade.side | ||
} | ||
}) | ||
|
||
cb(null, trades) | ||
}) | ||
}, | ||
|
||
getBalance: function(opts, cb) { | ||
var client = authedClient() | ||
client.api('balance', function(err, wallet) { | ||
if (err) return retry('getBalance', null, err) | ||
if (wallet.error) return retry('getBalance', null, wallet.error) | ||
|
||
var currency = opts.currency.toLowerCase() | ||
var asset = opts.asset.toLowerCase() | ||
|
||
var balance = { | ||
asset: 0, | ||
currency: 0 | ||
} | ||
|
||
balance.currency = wallet[currency + '_balance']; | ||
balance.asset = wallet[asset + '_balance']; | ||
|
||
balance.currency_hold = wallet[currency + '_reserved'] | ||
balance.asset_hold = wallet[asset + '_reserved'] | ||
cb(null, balance) | ||
}) | ||
}, | ||
|
||
getQuote: function(opts, cb) { | ||
var func_args = [].slice.call(arguments) | ||
|
||
var params = { | ||
book: joinProduct(opts.product_id) | ||
} | ||
|
||
var client = publicClient() | ||
client.api('ticker', params, function(err, quote) { | ||
if (err) return retry('getQuote', func_args, err) | ||
if (quote.error) return retry('getQuote', func_args, quote.error) | ||
|
||
var r = { | ||
bid: quote.bid, | ||
ask: quote.ask | ||
} | ||
|
||
cb(null, r) | ||
}) | ||
}, | ||
|
||
cancelOrder: function(opts, cb) { | ||
var func_args = [].slice.call(arguments) | ||
var params = { | ||
id: opts.order_id | ||
} | ||
|
||
var client = authedClient() | ||
client.api('cancel_order', params, function(err, body) { | ||
if (err) return retry('cancelOrder', func_args, err) | ||
if (body.error) return retry('cancelOrder', func_args, body.error) | ||
cb() | ||
}) | ||
}, | ||
|
||
buy: function(opts, cb) { | ||
var params = { | ||
amount: opts.size, | ||
book: joinProduct(opts.product_id) | ||
} | ||
|
||
if (opts.order_type === 'maker') { | ||
params.price = n(opts.price).format('0.00') | ||
} | ||
|
||
var client = authedClient() | ||
client.api('buy', params, function(err, body) { | ||
var order = { | ||
id: null, | ||
status: 'open', | ||
price: opts.price, | ||
size: opts.size, | ||
created_at: new Date().getTime(), | ||
filled_size: '0', | ||
ordertype: opts.order_type | ||
} | ||
|
||
if (err) return cb(err) | ||
if (body.error) return cb(body.error.message) | ||
|
||
if (opts.order_type === 'taker') { | ||
order.status = 'done' | ||
order.done_at = new Date().getTime(); | ||
|
||
if (body.orders_matched) { | ||
var asset_total = 0 | ||
var price_total = 0.0 | ||
var order_count = body.orders_matched.length | ||
for (var idx = 0; idx < order_count; idx++) { | ||
asset_total = asset_total + body.orders_matched[idx].amount | ||
price_total = price_total + (body.orders_matched[idx].amount * body.orfers_matched[idx].price) | ||
} | ||
|
||
order.price = price_total / asset_total | ||
order.size = asset_total | ||
} else { | ||
order.price = body.price | ||
order.size = body.amount | ||
} | ||
} | ||
|
||
order.id = body.id | ||
orders['~' + body.id] = order | ||
cb(null, order) | ||
}) | ||
}, | ||
|
||
sell: function(opts, cb) { | ||
var params = { | ||
amount: opts.size, | ||
book: joinProduct(opts.product_id) | ||
} | ||
|
||
if (opts.order_type === 'maker' && typeof opts.type === 'undefined') { | ||
params.price = n(opts.price).format('0.00') | ||
} | ||
|
||
var client = authedClient() | ||
client.api('sell', params, function(err, body) { | ||
var order = { | ||
id: null, | ||
status: 'open', | ||
price: opts.price, | ||
size: opts.size, | ||
created_at: new Date().getTime(), | ||
filled_size: '0', | ||
ordertype: opts.order_type | ||
} | ||
|
||
if (err) return cb(err) | ||
if (body.error) return cb(body.error.message) | ||
|
||
if (opts.order_type === 'taker') { | ||
order.status = 'done' | ||
order.done_at = new Date().getTime(); | ||
|
||
if (body.orders_matched) { | ||
var asset_total = 0 | ||
var price_total = 0.0 | ||
var order_count = body.orders_matched.length | ||
for (var idx = 0; idx < order_count; idx++) { | ||
asset_total = asset_total + body.orders_matched[idx].amount | ||
price_total = price_total + (body.orders_matched[idx].amount * body.orfers_matched[idx].price) | ||
} | ||
|
||
order.price = price_total / asset_total | ||
order.size = asset_total | ||
} else { | ||
order.price = body.price | ||
order.size = body.amount | ||
} | ||
} | ||
|
||
order.id = body.id | ||
orders['~' + body.id] = order | ||
cb(null, order) | ||
}) | ||
}, | ||
|
||
getOrder: function(opts, cb) { | ||
var order = orders['~' + opts.order_id] | ||
var params = { | ||
id: opts.order_id | ||
} | ||
|
||
var client = authedClient() | ||
client.api('lookup_order', params, function(err, body) { | ||
if (err) return cb(err) | ||
if (body.error) return cb(body.error.message) | ||
|
||
if (body.status === 2) { | ||
order.status = 'done' | ||
order.done_at = new Date().getTime() | ||
order.filled_size = body.amount | ||
return cb(null, order) | ||
} | ||
cb(null, order) | ||
}) | ||
}, | ||
|
||
// return the property used for range querying. | ||
getCursor: function(trade) { | ||
return trade.time | ||
} | ||
} | ||
return exchange | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"name": "zenbot_quadrigacx", | ||
"version": "0.0.1", | ||
"description": "Zenbot supporting code for QuadrigaCX", | ||
"dependencies": { | ||
"quadrigacx": "^0.0.7", | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
[ | ||
{ | ||
"id": "BTCUSD", | ||
"asset": "BTC", | ||
"currency": "USD", | ||
"min_size": "0.00001", | ||
"max_size": "10000", | ||
"increment": "0.00001", | ||
"label": "BTC/USD" | ||
}, | ||
{ | ||
"id": "BTCCAD", | ||
"asset": "BTC", | ||
"currency": "CAD", | ||
"min_size": "0.00001", | ||
"max_size": "10000", | ||
"increment": "0.00001", | ||
"label": "BTC/CAD" | ||
}, | ||
{ | ||
"id": "ETHCAD", | ||
"asset": "ETH", | ||
"currency": "CAD", | ||
"min_size": "0.00001", | ||
"max_size": "10000", | ||
"increment": "0.00001", | ||
"label": "ETH/CAD" | ||
}, | ||
{ | ||
"id": "ETHBTC", | ||
"asset": "ETH", | ||
"currency": "BTC", | ||
"min_size": "0.00001", | ||
"max_size": "1000000", | ||
"increment": "0.00001", | ||
"label": "ETH/BTC" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.