Snapshots
This commit is contained in:
1 parent
481986cc45
commit
b69a9416ca
3 files changed
+172
-102
No files matched your search
+56
-84
@@ -121,6 +121,7 @@
|
||||
const CONDITIONS_FOLDER_NAME = "Simulation-Conditions";
|
||||
const STRATEGIES_FOLDER_NAME = "Simulation-Strategies";
|
||||
const TRADES_FOLDER_NAME = "Simulation-Trades";
|
||||
const SNAPSHOTS_FOLDER_NAME = "Snapshots";
|
||||
|
||||
const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000;
|
||||
|
||||
@@ -129,6 +130,10 @@
|
||||
let strategiesArray
|
||||
let tradesArray
|
||||
|
||||
let snapshotHeaders
|
||||
let triggerOnSnapshot
|
||||
let takePositionSnapshot
|
||||
|
||||
let tradingSystem = {};
|
||||
|
||||
tradingSimulation.runSimulation(
|
||||
@@ -142,7 +147,7 @@
|
||||
writeFiles,
|
||||
callBackFunction)
|
||||
|
||||
function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray) {
|
||||
function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray, pSnapshotHeaders, pTriggerOnSnapshot, pTakePositionSnapshot) {
|
||||
|
||||
tradingSystem = pTradingSystem
|
||||
recordsArray = pRecordsArray
|
||||
@@ -150,11 +155,16 @@
|
||||
strategiesArray = pStrategiesArray
|
||||
tradesArray = pTradesArray
|
||||
|
||||
snapshotHeaders = pSnapshotHeaders
|
||||
triggerOnSnapshot = pTriggerOnSnapshot
|
||||
takePositionSnapshot = pTakePositionSnapshot
|
||||
|
||||
if (timeFrame > global.dailyFilePeriods[0][0]) {
|
||||
writeMarketFiles()
|
||||
} else {
|
||||
writeDailyFiles()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function writeMarketFiles() {
|
||||
@@ -474,7 +484,7 @@
|
||||
|
||||
}
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
writeSnapshotFiles();
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
@@ -489,97 +499,52 @@
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
function writeSnapshotFiles() {
|
||||
|
||||
function writeDailyFiles() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> Entering function."); }
|
||||
writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish)
|
||||
|
||||
writeRecordsFile();
|
||||
function onFinish() {
|
||||
writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* TODO : NEXT function is a work in progress, not used today */
|
||||
function writeFinalResults() {
|
||||
function writeSnapshotFile(snapshotArray, pFileName, callBack) {
|
||||
|
||||
try {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> Entering function."); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeSnapshotFile -> Entering function."); }
|
||||
|
||||
if ( // We only write this file if we are at the head of the market.
|
||||
Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS
|
||||
!==
|
||||
Math.trunc((new Date()).valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS
|
||||
) {
|
||||
writeRecordsFile();
|
||||
return;
|
||||
}
|
||||
|
||||
let separator = "";
|
||||
let fileRecordCounter = 0;
|
||||
|
||||
let fileContent = "";
|
||||
let separator = "\r\n";
|
||||
|
||||
for (let i = 0; i < recordsArray.length; i++) {
|
||||
|
||||
let record = recordsArray[i];
|
||||
|
||||
/* Will only add to the file the records of the current day */
|
||||
|
||||
if (record.begin < currentDay.valueOf()) { continue; }
|
||||
|
||||
fileContent = fileContent + separator + '[' +
|
||||
record.begin + "," +
|
||||
record.end + "," +
|
||||
record.type + "," +
|
||||
record.marketRate + "," +
|
||||
record.amount + "," +
|
||||
record.balanceA + "," +
|
||||
record.balanceB + "," +
|
||||
record.profit + "," +
|
||||
record.lastTradeProfitLoss + "," +
|
||||
record.stopLoss + "," +
|
||||
record.roundtrips + "," +
|
||||
record.hits + "," +
|
||||
record.fails + "," +
|
||||
record.hitRatio + "," +
|
||||
record.ROI + "," +
|
||||
record.periods + "," +
|
||||
record.days + "," +
|
||||
record.anualizedRateOfReturn + "," +
|
||||
record.positionRate + "," +
|
||||
record.lastTradeROI + "," +
|
||||
record.strategy + "," +
|
||||
record.strategyStageNumber + "," +
|
||||
record.takeProfit + "," +
|
||||
record.stopLossPhase + "," +
|
||||
record.takeProfitPhase + "," +
|
||||
JSON.stringify(record.executionRecord) + "," +
|
||||
record.positionSize + "," +
|
||||
record.initialBalanceA + "," +
|
||||
record.minimumBalanceA + "," +
|
||||
record.maximumBalanceA + "," +
|
||||
record.initialBalanceB + "," +
|
||||
record.minimumBalanceB + "," +
|
||||
record.maximumBalanceB + "," +
|
||||
record.baseAsset + "," +
|
||||
record.quotedAsset + "," +
|
||||
record.marketBaseAsset + "," +
|
||||
record.marketQuotedAsset + "," +
|
||||
record.positionPeriods + "," +
|
||||
record.positionDays + "," +
|
||||
record.distanceToLastTriggerOn + "," +
|
||||
record.distanceToLastTriggerOff + "," +
|
||||
record.distanceToLastTakePosition + "," +
|
||||
record.distanceToLastClosePosition + "]";
|
||||
|
||||
if (separator === "") { separator = ","; }
|
||||
parseRecord(snapshotHeaders)
|
||||
|
||||
for (let i = 0; i < snapshotArray.length; i++) {
|
||||
let record = snapshotArray[i];
|
||||
parseRecord(record)
|
||||
fileRecordCounter++;
|
||||
}
|
||||
|
||||
function parseRecord(record) {
|
||||
for (let j = 0; j < record.length; j++) {
|
||||
let property = record[j]
|
||||
|
||||
fileContent = fileContent + '' + property
|
||||
if (j !== record.length - 1) {
|
||||
fileContent = fileContent + ","
|
||||
}
|
||||
}
|
||||
fileContent = fileContent + separator
|
||||
|
||||
}
|
||||
|
||||
fileContent = "[" + fileContent + "]";
|
||||
fileContent = "" + fileContent + "";
|
||||
|
||||
let fileName = 'Data.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel;
|
||||
let fileName = pFileName + '.csv';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SNAPSHOTS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel;
|
||||
filePath += '/' + fileName
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated);
|
||||
@@ -588,35 +553,42 @@
|
||||
|
||||
try {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> Entering function."); }
|
||||
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> fileContent = " + fileContent); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> Entering function."); }
|
||||
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> fileContent = " + fileContent); }
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> filePath = " + filePath);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> filePath = " + filePath);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset);
|
||||
|
||||
callBackFunction(err);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
writeRecordsFile();
|
||||
callBack(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function writeDailyFiles() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> Entering function."); }
|
||||
|
||||
writeRecordsFile();
|
||||
|
||||
function writeRecordsFile() {
|
||||
|
||||
try {
|
||||
|
||||
+107
-9
@@ -39,6 +39,10 @@
|
||||
} else {
|
||||
processingDailyFiles = true
|
||||
}
|
||||
|
||||
let snapshotHeaders
|
||||
let triggerOnSnapshot = [];
|
||||
let takePositionSnapshot = [];
|
||||
|
||||
let recordsArray = [];
|
||||
let conditionsArray = [];
|
||||
@@ -500,6 +504,14 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Entering function."); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Processing candle # " + currentCandleIndex); }
|
||||
|
||||
/* We are going to take snapshots of the values of indicators at key moments of the sumulation. */
|
||||
|
||||
let snapshotKeys = new Map()
|
||||
snapshotLoopHeaders = []
|
||||
snapshotDataRecord = []
|
||||
addToTriggerOnSnapshot = false
|
||||
addToTakePositionSnapshot = false
|
||||
|
||||
let announcementsToBeMade = []
|
||||
let candle = candles[currentCandleIndex];
|
||||
|
||||
@@ -767,7 +779,9 @@
|
||||
if (initialDefinition.positionSize !== undefined) {
|
||||
if (initialDefinition.positionSize.formula !== undefined) {
|
||||
try {
|
||||
positionSize = eval(initialDefinition.positionSize.formula.code);
|
||||
let code = initialDefinition.positionSize.formula.code
|
||||
positionSize = eval(code);
|
||||
addCodeToSnapshot(code)
|
||||
} catch (err) {
|
||||
initialDefinition.positionSize.formula.error = err.message
|
||||
}
|
||||
@@ -794,7 +808,9 @@
|
||||
if (initialDefinition.positionRate !== undefined) {
|
||||
if (initialDefinition.positionRate.formula !== undefined) {
|
||||
try {
|
||||
let code = initialDefinition.positionRate.formula.code
|
||||
positionRate = eval(initialDefinition.positionRate.formula.code);
|
||||
addCodeToSnapshot(code)
|
||||
} catch (err) {
|
||||
initialDefinition.positionRate.formula.error = err.message
|
||||
}
|
||||
@@ -821,7 +837,9 @@
|
||||
|
||||
if (phase.formula !== undefined) {
|
||||
try {
|
||||
formulaValue = eval(phase.formula.code);
|
||||
let code = phase.formula.code
|
||||
formulaValue = eval(code);
|
||||
addCodeToSnapshot(code)
|
||||
if (formulaValue === Infinity) {
|
||||
formulaError = "Formula evaluates to Infinity."
|
||||
formulaValue = MAX_STOP_LOSS_VALUE
|
||||
@@ -910,7 +928,9 @@
|
||||
|
||||
if (phase.formula !== undefined) {
|
||||
try {
|
||||
formulaValue = eval(phase.formula.code);
|
||||
let code = phase.formula.code
|
||||
formulaValue = eval(code);
|
||||
addCodeToSnapshot(code)
|
||||
if (formulaValue === Infinity) {
|
||||
formulaValue = MAX_TAKE_PROFIT_VALUE
|
||||
if (takeProfitStage === 'Open Stage') {
|
||||
@@ -1007,7 +1027,9 @@
|
||||
|
||||
if (phase.formula !== undefined) {
|
||||
try {
|
||||
formulaValue = eval(phase.formula.code);
|
||||
let code = phase.formula.code
|
||||
formulaValue = eval(code);
|
||||
addCodeToSnapshot(code)
|
||||
if (formulaValue === Infinity) {
|
||||
formulaError = ""
|
||||
formulaValue = MAX_STOP_LOSS_VALUE
|
||||
@@ -1096,7 +1118,9 @@
|
||||
|
||||
if (phase.formula !== undefined) {
|
||||
try {
|
||||
formulaValue = eval(phase.formula.code);
|
||||
let code = phase.formula.code
|
||||
formulaValue = eval(code);
|
||||
addCodeToSnapshot(code)
|
||||
if (formulaValue === Infinity) {
|
||||
formulaError = "Formula evaluates to Infinity."
|
||||
formulaValue = MAX_TAKE_PROFIT_VALUE
|
||||
@@ -1181,7 +1205,9 @@
|
||||
let value
|
||||
|
||||
try {
|
||||
value = eval(node.code);
|
||||
let code = node.code
|
||||
value = eval(code);
|
||||
addCodeToSnapshot(code)
|
||||
} catch (err) {
|
||||
/*
|
||||
One possible error is that the conditions references a .previous that is undefined. For this
|
||||
@@ -1319,6 +1345,7 @@
|
||||
}
|
||||
|
||||
checkAnnouncements(triggerStage.triggerOn)
|
||||
addToTriggerOnSnapshot = true
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Switching to Trigger Stage because conditions at Trigger On Event were met."); }
|
||||
break;
|
||||
@@ -1452,6 +1479,7 @@
|
||||
currentTrade.takePositionSituation = situation.name
|
||||
|
||||
checkAnnouncements(triggerStage.takePosition)
|
||||
addToTakePositionSnapshot = true
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Conditions at the Take Position Event were met."); }
|
||||
break;
|
||||
@@ -2186,6 +2214,7 @@
|
||||
}
|
||||
|
||||
takePositionAtSimulation()
|
||||
|
||||
return
|
||||
|
||||
function putOpeningOrder() {
|
||||
@@ -2914,6 +2943,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Add to Snapshot */
|
||||
if (addToTakePositionSnapshot === true) {
|
||||
takePositionSnapshot.push(snapshotDataRecord)
|
||||
addToTakePositionSnapshot = false
|
||||
}
|
||||
|
||||
if (addToTriggerOnSnapshot === true) {
|
||||
triggerOnSnapshot.push(snapshotDataRecord)
|
||||
addToTriggerOnSnapshot = false
|
||||
}
|
||||
|
||||
if (snapshotHeaders === undefined) {
|
||||
snapshotHeaders = JSON.parse(JSON.stringify(snapshotLoopHeaders))
|
||||
}
|
||||
|
||||
makeAnnoucements() // After everything at the simulation level was done, we will do the annoucements that are pending.
|
||||
}
|
||||
|
||||
@@ -3005,10 +3049,63 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addCodeToSnapshot(code) {
|
||||
if (code === undefined) { return }
|
||||
|
||||
try {
|
||||
let instructionsArray = code.split(' ')
|
||||
for (let i = 0; i < instructionsArray.length; i++) {
|
||||
let instruction = instructionsArray[i]
|
||||
instruction = instruction.replace('(', '')
|
||||
instruction = instruction.replace(')', '')
|
||||
instruction = instruction.replace(/</g, '')
|
||||
instruction = instruction.replace(/>/g, '')
|
||||
instruction = instruction.replace(/<=/g, '')
|
||||
instruction = instruction.replace(/>=/g, '')
|
||||
instruction = instruction.replace(/!=/g, '')
|
||||
instruction = instruction.replace(/!==/g, '')
|
||||
instruction = instruction.replace(/==/g, '')
|
||||
instruction = instruction.replace(/===/g, '')
|
||||
if (instruction.indexOf('chart') >= 0) {
|
||||
let parts = instruction.split('.')
|
||||
let timeFrame = parts[1]
|
||||
let product = parts[2]
|
||||
let property
|
||||
checkPrevious(3)
|
||||
function checkPrevious(index) {
|
||||
property = parts[index]
|
||||
if (property === 'previous') {
|
||||
product = product + '.previous'
|
||||
checkPrevious(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10
|
||||
if (timeFrame !== 'atAnyTimeFrame') {
|
||||
timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7)
|
||||
}
|
||||
let key = timeFrame + '-' + product + '-' + property
|
||||
let existingKey = snapshotKeys.get(key)
|
||||
|
||||
if (existingKey === undefined) {// means that at the current loop this property of this product was not used before.
|
||||
snapshotKeys.set(key, key)
|
||||
snapshotLoopHeaders.push(key)
|
||||
|
||||
let value = eval(instruction)
|
||||
snapshotDataRecord.push(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] runSimulation -> addCodeToSnapshot -> code = " + code);
|
||||
logger.write(MODULE_NAME, "[ERROR] runSimulation -> addCodeToSnapshot -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function controlLoop() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> controlLoop -> Entering function."); }
|
||||
|
||||
@@ -3114,7 +3211,7 @@
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> callback -> recordsArray.length = " + recordsArray.length); }
|
||||
|
||||
callback(tradingSystem, recordsArray, conditionsArray, strategiesArray, tradesArray);
|
||||
callback(tradingSystem, recordsArray, conditionsArray, strategiesArray, tradesArray, snapshotHeaders, triggerOnSnapshot, takePositionSnapshot);
|
||||
}
|
||||
|
||||
function getElement(pArray, currentCandle, datasetName) {
|
||||
@@ -3156,6 +3253,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] runSimulation -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
|
||||
Generated
+9
-9
@@ -5,9 +5,9 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ccxt": {
|
||||
"version": "1.25.80",
|
||||
"resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.25.80.tgz",
|
||||
"integrity": "sha512-AUWagxMa8PMexRabbktWezlmVacj6L2Ep4844ebIXUOyME2Ommg7O/9/pq0F2Ne1IHcJnNJzHrMYQvkh2gBtlw=="
|
||||
"version": "1.27.45",
|
||||
"resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.27.45.tgz",
|
||||
"integrity": "sha512-4ADlbcIWDzIWjtLvsrtux5jMFj7pU4kVuiqtI2Btq6NEhQ4r/4+LirXdEFlCF6/wIU3XSHabxMd/8nu8HC1KJw=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
@@ -48,9 +48,9 @@
|
||||
"integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ=="
|
||||
},
|
||||
"telegraf": {
|
||||
"version": "3.37.0",
|
||||
"resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.37.0.tgz",
|
||||
"integrity": "sha512-V3448qwfOolBqkIc87yxjW4zMvR2P6AIF24pPTlX9WhZPwA1TF/x3nQhnWPRLtGh2SJuvDcr83iTkXPXT7Opnw==",
|
||||
"version": "3.38.0",
|
||||
"resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.38.0.tgz",
|
||||
"integrity": "sha512-va4VlrKWp64JrowFoZX/NPzzA6q38kvaIukVXOWFO1V+jR1G8+hCfgJy4TX8Z3rwLJzwaBEet1QhikHDRZWl3A==",
|
||||
"requires": {
|
||||
"debug": "4.1.1",
|
||||
"minimist": "1.2.5",
|
||||
@@ -66,9 +66,9 @@
|
||||
"integrity": "sha512-njVv1EAhIZnmQVLocZEADYUyqA1WIXuVcDYlsp+mXua/XB0pxx+PKtMSPeZ/EE4wPWTw9h/hA9ASTT6yQelkiw=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz",
|
||||
"integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ=="
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz",
|
||||
"integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w=="
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user