UNPKG

metaapi.cloud-sdk

Version:

SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)

160 lines (159 loc) 16.3 kB
'use strict'; function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _async_to_generator(fn) { return function() { var self = this, args = arguments; return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } import HistoryDatabase from './historyDatabase'; import LoggerManager from '../../logger'; import fs from 'fs'; import path from 'path'; let FilesystemHistoryDatabase = class FilesystemHistoryDatabase extends HistoryDatabase { /** * Returns history database instance * @returns {HistoryDatabase} history database instance */ static getInstance() { if (!FilesystemHistoryDatabase.instance) { FilesystemHistoryDatabase.instance = new FilesystemHistoryDatabase(); } return FilesystemHistoryDatabase.instance; } /** * Loads history from database * @param {string} accountId account id * @param {string} application application name * @return {Promise<{deals: Array<MetatraderDeal>, historyOrders: Array<MetatraderOrder>}>} full account history */ loadHistory(accountId, application) { var _this = this; return _async_to_generator(function*() { let { dealsFile, historyOrdersFile } = yield _this._getDbLocation(accountId, application); let deals = yield _this._readDb(accountId, dealsFile); if (deals.length && Array.isArray(deals[0])) { _this.clear(accountId, application); deals = []; } deals.forEach((deal)=>deal.time = new Date(deal.time)); let historyOrders = yield _this._readDb(accountId, historyOrdersFile); if (historyOrders.length && Array.isArray(historyOrders[0])) { _this.clear(accountId, application); historyOrders = []; } historyOrders.forEach((historyOrder)=>{ historyOrder.time = new Date(historyOrder.time); historyOrder.doneTime = new Date(historyOrder.doneTime); }); return { deals, historyOrders }; })(); } /** * Removes history from database * @param {string} accountId account id * @param {string} application application name * @return {Promise} promise resolving when the history is removed */ clear(accountId, application) { var _this = this; return _async_to_generator(function*() { let { dealsFile, historyOrdersFile } = yield _this._getDbLocation(accountId, application); if (fs.existsSync(dealsFile)) { yield fs.promises.unlink(dealsFile); } if (fs.existsSync(historyOrdersFile)) { yield fs.promises.unlink(historyOrdersFile); } })(); } /** * Flushes the new history to db * @param {string} accountId account id * @param {string} application application name * @param {Array<MetatraderOrder>} newHistoryOrders history orders to save to db * @param {Array<MetatraderDeal>} newDeals deals to save to db * @return {Promise} promise resolving when the history is flushed */ flush(accountId, application, newHistoryOrders, newDeals) { var _this = this; return _async_to_generator(function*() { let { dealsFile, historyOrdersFile } = yield _this._getDbLocation(accountId, application); yield _this._appendDb(historyOrdersFile, newHistoryOrders); yield _this._appendDb(dealsFile, newDeals); })(); } _getDbLocation(accountId, application) { return _async_to_generator(function*() { let dir = path.join(process.cwd(), '.metaapi'); yield fs.promises.mkdir(dir, { recursive: true }); return { dealsFile: path.join(dir, `${accountId}-${application}-deals.bin`), historyOrdersFile: path.join(dir, `${accountId}-${application}-historyOrders.bin`) }; })(); } _readDb(accountId, file) { var _this = this; return _async_to_generator(function*() { if (!fs.existsSync(file)) { return []; } try { let data = yield fs.promises.readFile(file, 'utf-8'); let lines = data.split('\n'); let result = []; for (let line of lines){ if (line.length) { result.push(JSON.parse(line)); } } return result; } catch (err) { _this._logger.warn(`${accountId}: failed to read history db, will remove ${file} now`, err); yield fs.promises.unlink(file); return []; } })(); } _appendDb(file, records) { return _async_to_generator(function*() { if (records && records.length) { yield fs.promises.appendFile(file, records.map((r)=>JSON.stringify(r) + '\n').join(''), 'utf-8'); } })(); } /** * Constructs the class instance */ constructor(){ super(); this._logger = LoggerManager.getLogger('FilesystemHistoryDatabase'); } }; /** * Provides access to history database stored on filesystem */ export { FilesystemHistoryDatabase as default }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBIaXN0b3J5RGF0YWJhc2UgZnJvbSAnLi9oaXN0b3J5RGF0YWJhc2UnO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vLi4vbG9nZ2VyJztcbmltcG9ydCBmcyBmcm9tICdmcyc7XG5pbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcblxuLyoqXG4gKiBQcm92aWRlcyBhY2Nlc3MgdG8gaGlzdG9yeSBkYXRhYmFzZSBzdG9yZWQgb24gZmlsZXN5c3RlbVxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBGaWxlc3lzdGVtSGlzdG9yeURhdGFiYXNlIGV4dGVuZHMgSGlzdG9yeURhdGFiYXNlIHtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyB0aGUgY2xhc3MgaW5zdGFuY2VcbiAgICovXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5fbG9nZ2VyID0gTG9nZ2VyTWFuYWdlci5nZXRMb2dnZXIoJ0ZpbGVzeXN0ZW1IaXN0b3J5RGF0YWJhc2UnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGhpc3RvcnkgZGF0YWJhc2UgaW5zdGFuY2VcbiAgICogQHJldHVybnMge0hpc3RvcnlEYXRhYmFzZX0gaGlzdG9yeSBkYXRhYmFzZSBpbnN0YW5jZVxuICAgKi9cbiAgc3RhdGljIGdldEluc3RhbmNlKCkge1xuICAgIGlmICghRmlsZXN5c3RlbUhpc3RvcnlEYXRhYmFzZS5pbnN0YW5jZSkge1xuICAgICAgRmlsZXN5c3RlbUhpc3RvcnlEYXRhYmFzZS5pbnN0YW5jZSA9IG5ldyBGaWxlc3lzdGVtSGlzdG9yeURhdGFiYXNlKCk7XG4gICAgfVxuICAgIHJldHVybiBGaWxlc3lzdGVtSGlzdG9yeURhdGFiYXNlLmluc3RhbmNlO1xuICB9XG5cbiAgLyoqXG4gICAqIExvYWRzIGhpc3RvcnkgZnJvbSBkYXRhYmFzZVxuICAgKiBAcGFyYW0ge3N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtzdHJpbmd9IGFwcGxpY2F0aW9uIGFwcGxpY2F0aW9uIG5hbWVcbiAgICogQHJldHVybiB7UHJvbWlzZTx7ZGVhbHM6IEFycmF5PE1ldGF0cmFkZXJEZWFsPiwgaGlzdG9yeU9yZGVyczogQXJyYXk8TWV0YXRyYWRlck9yZGVyPn0+fSBmdWxsIGFjY291bnQgaGlzdG9yeVxuICAgKi9cbiAgYXN5bmMgbG9hZEhpc3RvcnkoYWNjb3VudElkLCBhcHBsaWNhdGlvbikge1xuICAgIGxldCB7ZGVhbHNGaWxlLCBoaXN0b3J5T3JkZXJzRmlsZX0gPSBhd2FpdCB0aGlzLl9nZXREYkxvY2F0aW9uKGFjY291bnRJZCwgYXBwbGljYXRpb24pO1xuICAgIGxldCBkZWFscyA9IGF3YWl0IHRoaXMuX3JlYWREYihhY2NvdW50SWQsIGRlYWxzRmlsZSk7XG4gICAgaWYoZGVhbHMubGVuZ3RoICYmIEFycmF5LmlzQXJyYXkoZGVhbHNbMF0pKSB7XG4gICAgICB0aGlzLmNsZWFyKGFjY291bnRJZCwgYXBwbGljYXRpb24pO1xuICAgICAgZGVhbHMgPSBbXTtcbiAgICB9XG4gICAgZGVhbHMuZm9yRWFjaChkZWFsID0+IGRlYWwudGltZSA9IG5ldyBEYXRlKGRlYWwudGltZSkpO1xuICAgIGxldCBoaXN0b3J5T3JkZXJzID0gYXdhaXQgdGhpcy5fcmVhZERiKGFjY291bnRJZCwgaGlzdG9yeU9yZGVyc0ZpbGUpO1xuICAgIGlmKGhpc3RvcnlPcmRlcnMubGVuZ3RoICYmIEFycmF5LmlzQXJyYXkoaGlzdG9yeU9yZGVyc1swXSkpIHtcbiAgICAgIHRoaXMuY2xlYXIoYWNjb3VudElkLCBhcHBsaWNhdGlvbik7XG4gICAgICBoaXN0b3J5T3JkZXJzID0gW107XG4gICAgfVxuICAgIGhpc3RvcnlPcmRlcnMuZm9yRWFjaChoaXN0b3J5T3JkZXIgPT4ge1xuICAgICAgaGlzdG9yeU9yZGVyLnRpbWUgPSBuZXcgRGF0ZShoaXN0b3J5T3JkZXIudGltZSk7XG4gICAgICBoaXN0b3J5T3JkZXIuZG9uZVRpbWUgPSBuZXcgRGF0ZShoaXN0b3J5T3JkZXIuZG9uZVRpbWUpO1xuICAgIH0pO1xuICAgIHJldHVybiB7ZGVhbHMsIGhpc3RvcnlPcmRlcnN9O1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgaGlzdG9yeSBmcm9tIGRhdGFiYXNlXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhY2NvdW50SWQgYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXBwbGljYXRpb24gYXBwbGljYXRpb24gbmFtZVxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHJlc29sdmluZyB3aGVuIHRoZSBoaXN0b3J5IGlzIHJlbW92ZWRcbiAgICovXG4gIGFzeW5jIGNsZWFyKGFjY291bnRJZCwgYXBwbGljYXRpb24pIHtcbiAgICBsZXQge2RlYWxzRmlsZSwgaGlzdG9yeU9yZGVyc0ZpbGV9ID0gYXdhaXQgdGhpcy5fZ2V0RGJMb2NhdGlvbihhY2NvdW50SWQsIGFwcGxpY2F0aW9uKTtcbiAgICBpZihmcy5leGlzdHNTeW5jKGRlYWxzRmlsZSkpIHtcbiAgICAgIGF3YWl0IGZzLnByb21pc2VzLnVubGluayhkZWFsc0ZpbGUpO1xuICAgIH1cbiAgICBpZihmcy5leGlzdHNTeW5jKGhpc3RvcnlPcmRlcnNGaWxlKSkge1xuICAgICAgYXdhaXQgZnMucHJvbWlzZXMudW5saW5rKGhpc3RvcnlPcmRlcnNGaWxlKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRmx1c2hlcyB0aGUgbmV3IGhpc3RvcnkgdG8gZGJcbiAgICogQHBhcmFtIHtzdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcHBsaWNhdGlvbiBhcHBsaWNhdGlvbiBuYW1lXG4gICAqIEBwYXJhbSB7QXJyYXk8TWV0YXRyYWRlck9yZGVyPn0gbmV3SGlzdG9yeU9yZGVycyBoaXN0b3J5IG9yZGVycyB0byBzYXZlIHRvIGRiXG4gICAqIEBwYXJhbSB7QXJyYXk8TWV0YXRyYWRlckRlYWw+fSBuZXdEZWFscyBkZWFscyB0byBzYXZlIHRvIGRiXG4gICAqIEByZXR1cm4ge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gdGhlIGhpc3RvcnkgaXMgZmx1c2hlZFxuICAgKi9cbiAgYXN5bmMgZmx1c2goYWNjb3VudElkLCBhcHBsaWNhdGlvbiwgbmV3SGlzdG9yeU9yZGVycywgbmV3RGVhbHMpIHtcbiAgICBsZXQge2RlYWxzRmlsZSwgaGlzdG9yeU9yZGVyc0ZpbGV9ID0gYXdhaXQgdGhpcy5fZ2V0RGJMb2NhdGlvbihhY2NvdW50SWQsIGFwcGxpY2F0aW9uKTtcbiAgICBhd2FpdCB0aGlzLl9hcHBlbmREYihoaXN0b3J5T3JkZXJzRmlsZSwgbmV3SGlzdG9yeU9yZGVycyk7XG4gICAgYXdhaXQgdGhpcy5fYXBwZW5kRGIoZGVhbHNGaWxlLCBuZXdEZWFscyk7XG4gIH1cblxuICBhc3luYyBfZ2V0RGJMb2NhdGlvbihhY2NvdW50SWQsIGFwcGxpY2F0aW9uKSB7XG4gICAgbGV0IGRpciA9IHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCAnLm1ldGFhcGknKTtcbiAgICBhd2FpdCBmcy5wcm9taXNlcy5ta2RpcihkaXIsIHtyZWN1cnNpdmU6IHRydWV9KTtcbiAgICByZXR1cm4ge1xuICAgICAgZGVhbHNGaWxlOiBwYXRoLmpvaW4oZGlyLCBgJHthY2NvdW50SWR9LSR7YXBwbGljYXRpb259LWRlYWxzLmJpbmApLFxuICAgICAgaGlzdG9yeU9yZGVyc0ZpbGU6IHBhdGguam9pbihkaXIsIGAke2FjY291bnRJZH0tJHthcHBsaWNhdGlvbn0taGlzdG9yeU9yZGVycy5iaW5gKVxuICAgIH07XG4gIH1cblxuICBhc3luYyBfcmVhZERiKGFjY291bnRJZCwgZmlsZSkge1xuICAgIGlmICghZnMuZXhpc3RzU3luYyhmaWxlKSkge1xuICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgICB0cnkge1xuICAgICAgbGV0IGRhdGEgPSBhd2FpdCBmcy5wcm9taXNlcy5yZWFkRmlsZShmaWxlLCAndXRmLTgnKTtcbiAgICAgIGxldCBsaW5lcyA9IGRhdGEuc3BsaXQoJ1xcbicpO1xuICAgICAgbGV0IHJlc3VsdCA9IFtdO1xuICAgICAgZm9yIChsZXQgbGluZSBvZiBsaW5lcykge1xuICAgICAgICBpZiAobGluZS5sZW5ndGgpIHtcbiAgICAgICAgICByZXN1bHQucHVzaChKU09OLnBhcnNlKGxpbmUpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci53YXJuKGAke2FjY291bnRJZH06IGZhaWxlZCB0byByZWFkIGhpc3RvcnkgZGIsIHdpbGwgcmVtb3ZlICR7ZmlsZX0gbm93YCwgZXJyKTtcbiAgICAgIGF3YWl0IGZzLnByb21pc2VzLnVubGluayhmaWxlKTtcbiAgICAgIHJldHVybiBbXTtcbiAgICB9XG4gIH1cblxuICBhc3luYyBfYXBwZW5kRGIoZmlsZSwgcmVjb3Jkcykge1xuICAgIGlmIChyZWNvcmRzICYmIHJlY29yZHMubGVuZ3RoKSB7XG4gICAgICBhd2FpdCBmcy5wcm9taXNlcy5hcHBlbmRGaWxlKGZpbGUsIHJlY29yZHMubWFwKHIgPT4gSlNPTi5zdHJpbmdpZnkocikgKyAnXFxuJykuam9pbignJyksICd1dGYtOCcpO1xuICAgIH1cbiAgfVxuXG59XG4iXSwibmFtZXMiOlsiSGlzdG9yeURhdGFiYXNlIiwiTG9nZ2VyTWFuYWdlciIsImZzIiwicGF0aCIsIkZpbGVzeXN0ZW1IaXN0b3J5RGF0YWJhc2UiLCJnZXRJbnN0YW5jZSIsImluc3RhbmNlIiwibG9hZEhpc3RvcnkiLCJhY2NvdW50SWQiLCJhcHBsaWNhdGlvbiIsImRlYWxzRmlsZSIsImhpc3RvcnlPcmRlcnNGaWxlIiwiX2dldERiTG9jYXRpb24iLCJkZWFscyIsIl9yZWFkRGIiLCJsZW5ndGgiLCJBcnJheSIsImlzQXJyYXkiLCJjbGVhciIsImZvckVhY2giLCJkZWFsIiwidGltZSIsIkRhdGUiLCJoaXN0b3J5T3JkZXJzIiwiaGlzdG9yeU9yZGVyIiwiZG9uZVRpbWUiLCJleGlzdHNTeW5jIiwicHJvbWlzZXMiLCJ1bmxpbmsiLCJmbHVzaCIsIm5ld0hpc3RvcnlPcmRlcnMiLCJuZXdEZWFscyIsIl9hcHBlbmREYiIsImRpciIsImpvaW4iLCJwcm9jZXNzIiwiY3dkIiwibWtkaXIiLCJyZWN1cnNpdmUiLCJmaWxlIiwiZGF0YSIsInJlYWRGaWxlIiwibGluZXMiLCJzcGxpdCIsInJlc3VsdCIsImxpbmUiLCJwdXNoIiwiSlNPTiIsInBhcnNlIiwiZXJyIiwiX2xvZ2dlciIsIndhcm4iLCJyZWNvcmRzIiwiYXBwZW5kRmlsZSIsIm1hcCIsInIiLCJzdHJpbmdpZnkiLCJjb25zdHJ1Y3RvciIsImdldExvZ2dlciJdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUVBLE9BQU9BLHFCQUFxQixvQkFBb0I7QUFDaEQsT0FBT0MsbUJBQW1CLGVBQWU7QUFDekMsT0FBT0MsUUFBUSxLQUFLO0FBQ3BCLE9BQU9DLFVBQVUsT0FBTztBQUtULElBQUEsQUFBTUMsNEJBQU4sTUFBTUEsa0NBQWtDSjtJQVVyRDs7O0dBR0MsR0FDRCxPQUFPSyxjQUFjO1FBQ25CLElBQUksQ0FBQ0QsMEJBQTBCRSxRQUFRLEVBQUU7WUFDdkNGLDBCQUEwQkUsUUFBUSxHQUFHLElBQUlGO1FBQzNDO1FBQ0EsT0FBT0EsMEJBQTBCRSxRQUFRO0lBQzNDO0lBRUE7Ozs7O0dBS0MsR0FDRCxBQUFNQyxZQUFZQyxTQUFTLEVBQUVDLFdBQVc7O2VBQXhDLG9CQUFBO1lBQ0UsSUFBSSxFQUFDQyxTQUFTLEVBQUVDLGlCQUFpQixFQUFDLEdBQUcsTUFBTSxNQUFLQyxjQUFjLENBQUNKLFdBQVdDO1lBQzFFLElBQUlJLFFBQVEsTUFBTSxNQUFLQyxPQUFPLENBQUNOLFdBQVdFO1lBQzFDLElBQUdHLE1BQU1FLE1BQU0sSUFBSUMsTUFBTUMsT0FBTyxDQUFDSixLQUFLLENBQUMsRUFB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