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)
126 lines (125 loc) • 15.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return FilesystemHistoryDatabase;
}
});
const _historyDatabase = /*#__PURE__*/ _interop_require_default(require("./historyDatabase"));
const _logger = /*#__PURE__*/ _interop_require_default(require("../../logger"));
const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
let FilesystemHistoryDatabase = class FilesystemHistoryDatabase extends _historyDatabase.default {
/**
* 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
*/ async loadHistory(accountId, application) {
let { dealsFile, historyOrdersFile } = await this._getDbLocation(accountId, application);
let deals = await 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 = await 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
*/ async clear(accountId, application) {
let { dealsFile, historyOrdersFile } = await this._getDbLocation(accountId, application);
if (_fs.default.existsSync(dealsFile)) {
await _fs.default.promises.unlink(dealsFile);
}
if (_fs.default.existsSync(historyOrdersFile)) {
await _fs.default.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
*/ async flush(accountId, application, newHistoryOrders, newDeals) {
let { dealsFile, historyOrdersFile } = await this._getDbLocation(accountId, application);
await this._appendDb(historyOrdersFile, newHistoryOrders);
await this._appendDb(dealsFile, newDeals);
}
async _getDbLocation(accountId, application) {
let dir = _path.default.join(process.cwd(), ".metaapi");
await _fs.default.promises.mkdir(dir, {
recursive: true
});
return {
dealsFile: _path.default.join(dir, `${accountId}-${application}-deals.bin`),
historyOrdersFile: _path.default.join(dir, `${accountId}-${application}-historyOrders.bin`)
};
}
async _readDb(accountId, file) {
if (!_fs.default.existsSync(file)) {
return [];
}
try {
let data = await _fs.default.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);
await _fs.default.promises.unlink(file);
return [];
}
}
async _appendDb(file, records) {
if (records && records.length) {
await _fs.default.promises.appendFile(file, records.map((r)=>JSON.stringify(r) + "\n").join(""), "utf-8");
}
}
/**
* Constructs the class instance
*/ constructor(){
super();
this._logger = _logger.default.getLogger("FilesystemHistoryDatabase");
}
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBIaXN0b3J5RGF0YWJhc2UgZnJvbSAnLi9oaXN0b3J5RGF0YWJhc2UnO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vLi4vbG9nZ2VyJztcbmltcG9ydCBmcyBmcm9tICdmcyc7XG5pbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcblxuLyoqXG4gKiBQcm92aWRlcyBhY2Nlc3MgdG8gaGlzdG9yeSBkYXRhYmFzZSBzdG9yZWQgb24gZmlsZXN5c3RlbVxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBGaWxlc3lzdGVtSGlzdG9yeURhdGFiYXNlIGV4dGVuZHMgSGlzdG9yeURhdGFiYXNlIHtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyB0aGUgY2xhc3MgaW5zdGFuY2VcbiAgICovXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5fbG9nZ2VyID0gTG9nZ2VyTWFuYWdlci5nZXRMb2dnZXIoJ0ZpbGVzeXN0ZW1IaXN0b3J5RGF0YWJhc2UnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGhpc3RvcnkgZGF0YWJhc2UgaW5zdGFuY2VcbiAgICogQHJldHVybnMge0hpc3RvcnlEYXRhYmFzZX0gaGlzdG9yeSBkYXRhYmFzZSBpbnN0YW5jZVxuICAgKi9cbiAgc3RhdGljIGdldEluc3RhbmNlKCkge1xuICAgIGlmICghRmlsZXN5c3RlbUhpc3RvcnlEYXRhYmFzZS5pbnN0YW5jZSkge1xuICAgICAgRmlsZXN5c3RlbUhpc3RvcnlEYXRhYmFzZS5pbnN0YW5jZSA9IG5ldyBGaWxlc3lzdGVtSGlzdG9yeURhdGFiYXNlKCk7XG4gICAgfVxuICAgIHJldHVybiBGaWxlc3lzdGVtSGlzdG9yeURhdGFiYXNlLmluc3RhbmNlO1xuICB9XG5cbiAgLyoqXG4gICAqIExvYWRzIGhpc3RvcnkgZnJvbSBkYXRhYmFzZVxuICAgKiBAcGFyYW0ge3N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtzdHJpbmd9IGFwcGxpY2F0aW9uIGFwcGxpY2F0aW9uIG5hbWVcbiAgICogQHJldHVybiB7UHJvbWlzZTx7ZGVhbHM6IEFycmF5PE1ldGF0cmFkZXJEZWFsPiwgaGlzdG9yeU9yZGVyczogQXJyYXk8TWV0YXRyYWRlck9yZGVyPn0+fSBmdWxsIGFjY291bnQgaGlzdG9yeVxuICAgKi9cbiAgYXN5bmMgbG9hZEhpc3RvcnkoYWNjb3VudElkLCBhcHBsaWNhdGlvbikge1xuICAgIGxldCB7ZGVhbHNGaWxlLCBoaXN0b3J5T3JkZXJzRmlsZX0gPSBhd2FpdCB0aGlzLl9nZXREYkxvY2F0aW9uKGFjY291bnRJZCwgYXBwbGljYXRpb24pO1xuICAgIGxldCBkZWFscyA9IGF3YWl0IHRoaXMuX3JlYWREYihhY2NvdW50SWQsIGRlYWxzRmlsZSk7XG4gICAgaWYoZGVhbHMubGVuZ3RoICYmIEFycmF5LmlzQXJyYXkoZGVhbHNbMF0pKSB7XG4gICAgICB0aGlzLmNsZWFyKGFjY291bnRJZCwgYXBwbGljYXRpb24pO1xuICAgICAgZGVhbHMgPSBbXTtcbiAgICB9XG4gICAgZGVhbHMuZm9yRWFjaChkZWFsID0+IGRlYWwudGltZSA9IG5ldyBEYXRlKGRlYWwudGltZSkpO1xuICAgIGxldCBoaXN0b3J5T3JkZXJzID0gYXdhaXQgdGhpcy5fcmVhZERiKGFjY291bnRJZCwgaGlzdG9yeU9yZGVyc0ZpbGUpO1xuICAgIGlmKGhpc3RvcnlPcmRlcnMubGVuZ3RoICYmIEFycmF5LmlzQXJyYXkoaGlzdG9yeU9yZGVyc1swXSkpIHtcbiAgICAgIHRoaXMuY2xlYXIoYWNjb3VudElkLCBhcHBsaWNhdGlvbik7XG4gICAgICBoaXN0b3J5T3JkZXJzID0gW107XG4gICAgfVxuICAgIGhpc3RvcnlPcmRlcnMuZm9yRWFjaChoaXN0b3J5T3JkZXIgPT4ge1xuICAgICAgaGlzdG9yeU9yZGVyLnRpbWUgPSBuZXcgRGF0ZShoaXN0b3J5T3JkZXIudGltZSk7XG4gICAgICBoaXN0b3J5T3JkZXIuZG9uZVRpbWUgPSBuZXcgRGF0ZShoaXN0b3J5T3JkZXIuZG9uZVRpbWUpO1xuICAgIH0pO1xuICAgIHJldHVybiB7ZGVhbHMsIGhpc3RvcnlPcmRlcnN9O1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgaGlzdG9yeSBmcm9tIGRhdGFiYXNlXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhY2NvdW50SWQgYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXBwbGljYXRpb24gYXBwbGljYXRpb24gbmFtZVxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHJlc29sdmluZyB3aGVuIHRoZSBoaXN0b3J5IGlzIHJlbW92ZWRcbiAgICovXG4gIGFzeW5jIGNsZWFyKGFjY291bnRJZCwgYXBwbGljYXRpb24pIHtcbiAgICBsZXQge2RlYWxzRmlsZSwgaGlzdG9yeU9yZGVyc0ZpbGV9ID0gYXdhaXQgdGhpcy5fZ2V0RGJMb2NhdGlvbihhY2NvdW50SWQsIGFwcGxpY2F0aW9uKTtcbiAgICBpZihmcy5leGlzdHNTeW5jKGRlYWxzRmlsZSkpIHtcbiAgICAgIGF3YWl0IGZzLnByb21pc2VzLnVubGluayhkZWFsc0ZpbGUpO1xuICAgIH1cbiAgICBpZihmcy5leGlzdHNTeW5jKGhpc3RvcnlPcmRlcnNGaWxlKSkge1xuICAgICAgYXdhaXQgZnMucHJvbWlzZXMudW5saW5rKGhpc3RvcnlPcmRlcnNGaWxlKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRmx1c2hlcyB0aGUgbmV3IGhpc3RvcnkgdG8gZGJcbiAgICogQHBhcmFtIHtzdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcHBsaWNhdGlvbiBhcHBsaWNhdGlvbiBuYW1lXG4gICAqIEBwYXJhbSB7QXJyYXk8TWV0YXRyYWRlck9yZGVyPn0gbmV3SGlzdG9yeU9yZGVycyBoaXN0b3J5IG9yZGVycyB0byBzYXZlIHRvIGRiXG4gICAqIEBwYXJhbSB7QXJyYXk8TWV0YXRyYWRlckRlYWw+fSBuZXdEZWFscyBkZWFscyB0byBzYXZlIHRvIGRiXG4gICAqIEByZXR1cm4ge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gdGhlIGhpc3RvcnkgaXMgZmx1c2hlZFxuICAgKi9cbiAgYXN5bmMgZmx1c2goYWNjb3VudElkLCBhcHBsaWNhdGlvbiwgbmV3SGlzdG9yeU9yZGVycywgbmV3RGVhbHMpIHtcbiAgICBsZXQge2RlYWxzRmlsZSwgaGlzdG9yeU9yZGVyc0ZpbGV9ID0gYXdhaXQgdGhpcy5fZ2V0RGJMb2NhdGlvbihhY2NvdW50SWQsIGFwcGxpY2F0aW9uKTtcbiAgICBhd2FpdCB0aGlzLl9hcHBlbmREYihoaXN0b3J5T3JkZXJzRmlsZSwgbmV3SGlzdG9yeU9yZGVycyk7XG4gICAgYXdhaXQgdGhpcy5fYXBwZW5kRGIoZGVhbHNGaWxlLCBuZXdEZWFscyk7XG4gIH1cblxuICBhc3luYyBfZ2V0RGJMb2NhdGlvbihhY2NvdW50SWQsIGFwcGxpY2F0aW9uKSB7XG4gICAgbGV0IGRpciA9IHBhdGguam9pbihwcm9jZXNzLmN3ZCgpLCAnLm1ldGFhcGknKTtcbiAgICBhd2FpdCBmcy5wcm9taXNlcy5ta2RpcihkaXIsIHtyZWN1cnNpdmU6IHRydWV9KTtcbiAgICByZXR1cm4ge1xuICAgICAgZGVhbHNGaWxlOiBwYXRoLmpvaW4oZGlyLCBgJHthY2NvdW50SWR9LSR7YXBwbGljYXRpb259LWRlYWxzLmJpbmApLFxuICAgICAgaGlzdG9yeU9yZGVyc0ZpbGU6IHBhdGguam9pbihkaXIsIGAke2FjY291bnRJZH0tJHthcHBsaWNhdGlvbn0taGlzdG9yeU9yZGVycy5iaW5gKVxuICAgIH07XG4gIH1cblxuICBhc3luYyBfcmVhZERiKGFjY291bnRJZCwgZmlsZSkge1xuICAgIGlmICghZnMuZXhpc3RzU3luYyhmaWxlKSkge1xuICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgICB0cnkge1xuICAgICAgbGV0IGRhdGEgPSBhd2FpdCBmcy5wcm9taXNlcy5yZWFkRmlsZShmaWxlLCAndXRmLTgnKTtcbiAgICAgIGxldCBsaW5lcyA9IGRhdGEuc3BsaXQoJ1xcbicpO1xuICAgICAgbGV0IHJlc3VsdCA9IFtdO1xuICAgICAgZm9yIChsZXQgbGluZSBvZiBsaW5lcykge1xuICAgICAgICBpZiAobGluZS5sZW5ndGgpIHtcbiAgICAgICAgICByZXN1bHQucHVzaChKU09OLnBhcnNlKGxpbmUpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci53YXJuKGAke2FjY291bnRJZH06IGZhaWxlZCB0byByZWFkIGhpc3RvcnkgZGIsIHdpbGwgcmVtb3ZlICR7ZmlsZX0gbm93YCwgZXJyKTtcbiAgICAgIGF3YWl0IGZzLnByb21pc2VzLnVubGluayhmaWxlKTtcbiAgICAgIHJldHVybiBbXTtcbiAgICB9XG4gIH1cblxuICBhc3luYyBfYXBwZW5kRGIoZmlsZSwgcmVjb3Jkcykge1xuICAgIGlmIChyZWNvcmRzICYmIHJlY29yZHMubGVuZ3RoKSB7XG4gICAgICBhd2FpdCBmcy5wcm9taXNlcy5hcHBlbmRGaWxlKGZpbGUsIHJlY29yZHMubWFwKHIgPT4gSlNPTi5zdHJpbmdpZnkocikgKyAnXFxuJykuam9pbignJyksICd1dGYtOCcpO1xuICAgIH1cbiAgfVxuXG59XG4iXSwibmFtZXMiOlsiRmlsZXN5c3RlbUhpc3RvcnlEYXRhYmFzZSIsIkhpc3RvcnlEYXRhYmFzZSIsImdldEluc3RhbmNlIiwiaW5zdGFuY2UiLCJsb2FkSGlzdG9yeSIsImFjY291bnRJZCIsImFwcGxpY2F0aW9uIiwiZGVhbHNGaWxlIiwiaGlzdG9yeU9yZGVyc0ZpbGUiLCJfZ2V0RGJMb2NhdGlvbiIsImRlYWxzIiwiX3JlYWREYiIsImxlbmd0aCIsIkFycmF5IiwiaXNBcnJheSIsImNsZWFyIiwiZm9yRWFjaCIsImRlYWwiLCJ0aW1lIiwiRGF0ZSIsImhpc3RvcnlPcmRlcnMiLCJoaXN0b3J5T3JkZXIiLCJkb25lVGltZSIsImZzIiwiZXhpc3RzU3luYyIsInByb21pc2VzIiwidW5saW5rIiwiZmx1c2giLCJuZXdIaXN0b3J5T3JkZXJzIiwibmV3RGVhbHMiLCJfYXBwZW5kRGIiLCJkaXIiLCJwYXRoIiwiam9pbiIsInByb2Nlc3MiLCJjd2QiLCJta2RpciIsInJlY3Vyc2l2ZSIsImZpbGUiLCJkYXRhIiwicmVhZEZpbGUiLCJsaW5lcyIsInNwbGl0IiwicmVzdWx0IiwibGluZSIsInB1c2giLCJKU09OIiwicGFyc2UiLCJlcnIiLCJfbG9nZ2VyIiwid2FybiIsInJlY29yZHMiLCJhcHBlbmRGaWxlIiwibWFwIiwiciIsInN0cmluZ2lmeSIsImNvbnN0cnVjdG9yIiwiTG9nZ2VyTWFuYWdlciIsImdldExvZ2dlciJdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7ZUFVcUJBOzs7d0VBUk87K0RBQ0Y7MkRBQ1g7NkRBQ0U7Ozs7OztBQUtGLElBQUEsQUFBTUEsNEJBQU4sTUFBTUEsa0NBQWtDQyx3QkFBZTtJQVVwRTs7O0dBR0MsR0FDRCxPQUFPQyxjQUFjO1FBQ25CLElBQUksQ0FBQ0YsMEJBQTBCRyxRQUFRLEVBQUU7WUFDdkNILDBCQUEwQkcsUUFBUSxHQUFHLElBQUlIO1FBQzNDO1FBQ0EsT0FBT0EsMEJBQTBCRyxRQUFRO0lBQzNDO0lBRUE7Ozs7O0dBS0MsR0FDRCxNQUFNQyxZQUFZQyxTQUFTLEVBQUVDLFdBQVcsRUFBRTtRQUN4QyxJQUFJLEVBQUNDLFNBQVMsRUFBRUMsaUJBQWlCLEVBQUMsR0FBRyxNQUFNLElBQUksQ0FBQ0MsY0FBYyxDQUFDSixXQUFXQztRQUMxRSxJQUFJSSxRQUFRLE1BQU0sSUFBSSxDQUFDQyxPQUFPLENBQUNOLFdBQVdFO1FBQzFDLElBQUdHLE1BQU1FLE1BQU0sSUFBSUMsTUFBTUMsT0FBTyxDQUFDSixLQUFLLENBQUMsRUFBRSxHQUFHO1lBQzFDLElBQUksQ0FBQ0ssS0FBSyxDQUFDVixXQUFXQztZQUN0QkksUUFBUSxFQUFFO1FBQ1o7UUFDQUEs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