UNPKG

bitdo2

Version:

A cryptocurrency order-rule executor

129 lines (109 loc) 3.45 kB
const _ = require('lodash'); const moment = require('moment'); const shortid = require('shortid'); const memoize = require('memoizee'); const log = require('../log'); /* eslint no-unused-vars: off */ module.exports = impl => { const ORDERS = {}; let BALANCE = 0; const cachedGetTicker = memoize(impl.getTicker, { promise: true, maxAge: 1000 }); /* eslint no-param-reassign: off */ function evaluateOrder(order) { if (order.settled) return Promise.resolve(order); // already settled log.info(`Evaluating order ${order.id}...`); const [currency, relation] = order.product.split('-'); return cachedGetTicker(currency, relation) .then(ticker => { if (order.side === 'buy' && order.price >= ticker.price) { log.info(`Simulating execution of buy for ${order.id}`); order.status = 'F'; order.settled = true; BALANCE -= order.price * order.size; } if (order.side === 'sell' && order.price <= ticker.price) { log.info(`Simulating execution of sell for ${order.id}`); order.status = 'F'; order.settled = true; BALANCE += order.price * order.size; } if (order.settled) log.warn(` Updated balance: ${BALANCE}`); return order; }); } const orderSimulator = { getOrder(orderId) { if (_.has(ORDERS, orderId)) return Promise.resolve(ORDERS[orderId]); return impl.getOrder(orderId); }, getOrders() { return impl.getOrders() .then(realOrders => { return _.map(ORDERS, order => order) .concat(realOrders); }); }, createLimitOrder(side, currency, relation, size, price) { log.warn(`Creating simulated ${side} order on ${currency}-${relation} at @${price} #${size}...`); if (price <= 0 || size <= 0) throw new Error('Not valid price or size'); if (!currency || !relation) throw new Error('Must specify currency and relation'); if (side !== 'buy' && side !== 'sell') throw new Error('Must be buy or sell'); const id = shortid(); const order = { id, status: 'O', settled: false, product: `${currency.toUpperCase()}-${relation.toUpperCase()}`, price, size, date: moment().format(), type: 'limit', side, fee: 0.0, }; ORDERS[id] = order; return evaluateOrder(order) .then(eOrder => { // Job to "evaluate" the order if (!eOrder.settled) { order.__interval = setInterval(() => { evaluateOrder(order) .then(e => { if (e.settled) clearInterval(e.__interval); }); }, 10 * 1000); } return { id, settled: eOrder.settled, }; }); }, cancelOrder(orderId) { log.warn('Trying to cancel simulated order (will not fall-through)...'); if (_.has(ORDERS, orderId)) { delete ORDERS[orderId]; return Promise.resolve({}); } return Promise.reject(new Error('No such order')); }, }; return new Proxy(impl, { get(obj, prop) { return _.get(orderSimulator, prop, obj[prop]); }, set(obj, prop) { return false; }, apply(obj, prop) { return null; }, }); };