Trading Simulation consuming indicators with new scheme
This commit is contained in:
1 parent
0fb685a34f
commit
3a25feef88
8 files changed
+3004
-84
No files matched your search
+5
-2
@@ -164,6 +164,7 @@
|
||||
*/
|
||||
|
||||
let jsonifiedArray = []
|
||||
let previous;
|
||||
|
||||
for (let i = 0; i < dataFile.length; i++) {
|
||||
|
||||
@@ -175,7 +176,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
record.previous = previous;
|
||||
jsonifiedArray.push(record);
|
||||
previous = record;
|
||||
}
|
||||
|
||||
return jsonifiedArray
|
||||
@@ -405,7 +408,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, outputPeriodLabel, callBackFunction) {
|
||||
function writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, timePeriodLabel, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -420,7 +423,7 @@
|
||||
}
|
||||
|
||||
let filePathRoot = contextSummary.devTeam + "/" + contextSummary.bot + "." + contextSummary.botVersion.major + "." + contextSummary.botVersion.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + contextSummary.dataSetVersion;
|
||||
let filePath = filePathRoot + "/Output/" + contextSummary.product + "/" + contextSummary.dataset + "/" + outputPeriodLabel + dateForPath;
|
||||
let filePath = filePathRoot + "/Output/" + contextSummary.product + "/" + contextSummary.dataset + "/" + timePeriodLabel + dateForPath;
|
||||
filePath += '/' + fileName
|
||||
|
||||
fileStorage.createTextFile(contextSummary.devTeam, filePath, fileContent + '\n', onFileCreated);
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function start(dataFiles, timePeriod, outputPeriodLabel, currentDay, interExecutionMemory, callBackFunction) {
|
||||
function start(dataFiles, timePeriod, timePeriodLabel, currentDay, interExecutionMemory, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
contextSummary.dataSetVersion = "dataSet.V1"
|
||||
|
||||
let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, callBackFunction)
|
||||
commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, outputPeriodLabel, callBackFunction)
|
||||
commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, timePeriodLabel, callBackFunction)
|
||||
}
|
||||
|
||||
|
||||
|
||||
+10
-10
@@ -63,7 +63,7 @@
|
||||
let USER_BOT_MODULE = require("./TradingBot")
|
||||
|
||||
botInstance = USER_BOT_MODULE.newTradingBot(bot, logger, UTILITIES, FILE_STORAGE);
|
||||
botInstance.initialize(callBackFunction);
|
||||
botInstance.initialize(pAssistant, callBackFunction);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
@@ -284,12 +284,12 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriodsMarketFiles -> periodsLoopBody -> Entering function."); }
|
||||
|
||||
const timePeriod = global.marketFilesPeriods[n][0];
|
||||
const outputPeriodLabel = global.marketFilesPeriods[n][1];
|
||||
const timePeriodLabel = global.marketFilesPeriods[n][1];
|
||||
|
||||
let dependencyIndex = 0;
|
||||
dataFiles = new Map();
|
||||
|
||||
if (bot.VALUES_TO_USE.timePeriod === outputPeriodLabel) {
|
||||
if (bot.VALUES_TO_USE.timePeriod === timePeriodLabel) {
|
||||
currentTimePeriod = global.marketFilesPeriods[n][0];
|
||||
currentOutputPeriodName = global.marketFilesPeriods[n][1];
|
||||
}
|
||||
@@ -320,7 +320,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
let filePath = dependency.product + '/' + dependency.dataSet + "/" + outputPeriodLabel;
|
||||
let filePath = dependency.product + '/' + dependency.dataSet + "/" + timePeriodLabel;
|
||||
|
||||
dataset.getTextFile(filePath, fileName, onFileReceived);
|
||||
|
||||
@@ -575,22 +575,22 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriodsDailyFiles -> periodsLoopBody -> Entering function."); }
|
||||
|
||||
const timePeriod = global.dailyFilePeriods[n][0];
|
||||
const outputPeriodLabel = global.dailyFilePeriods[n][1];
|
||||
const timePeriodLabel = global.dailyFilePeriods[n][1];
|
||||
|
||||
if (processConfig.framework.validPeriods !== undefined) {
|
||||
let validPeriod = false;
|
||||
for (let i = 0; i < processConfig.framework.validPeriods.length; i++) {
|
||||
let period = processConfig.framework.validPeriods[i];
|
||||
if (period === outputPeriodLabel) { validPeriod = true }
|
||||
if (period === timePeriodLabel) { validPeriod = true }
|
||||
}
|
||||
if (validPeriod === false) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriodsDailyFiles -> periodsLoopBody -> Discarding period for not being listed as a valid period. -> outputPeriodLabel = " + outputPeriodLabel); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriodsDailyFiles -> periodsLoopBody -> Discarding period for not being listed as a valid period. -> timePeriodLabel = " + timePeriodLabel); }
|
||||
periodsControlLoop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.VALUES_TO_USE.timePeriod === outputPeriodLabel) {
|
||||
if (bot.VALUES_TO_USE.timePeriod === timePeriodLabel) {
|
||||
currentTimePeriod = global.dailyFilePeriods[n][0];
|
||||
currentOutputPeriodName = global.dailyFilePeriods[n][1];
|
||||
}
|
||||
@@ -634,7 +634,7 @@
|
||||
let filePath
|
||||
|
||||
if (dependency.dataSet === "Multi-Period-Daily") {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + outputPeriodLabel + "/" + dateForPath;
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + timePeriodLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + dateForPath;
|
||||
}
|
||||
@@ -710,7 +710,7 @@
|
||||
let dateForPath = bot.multiPeriodProcessDatetime.getUTCFullYear() + '/' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.dataSet === "Multi-Period-Daily") {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + outputPeriodLabel + "/" + dateForPath;
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + timePeriodLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + dateForPath;
|
||||
}
|
||||
|
||||
+7
-7
@@ -370,16 +370,16 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriods -> periodsLoopBody -> Entering function."); }
|
||||
|
||||
const timePeriod = global.dailyFilePeriods[n][0];
|
||||
const outputPeriodLabel = global.dailyFilePeriods[n][1];
|
||||
const timePeriodLabel = global.dailyFilePeriods[n][1];
|
||||
|
||||
if (processConfig.framework.validPeriods !== undefined) {
|
||||
let validPeriod = false;
|
||||
for (let i = 0; i < processConfig.framework.validPeriods.length; i++) {
|
||||
let period = processConfig.framework.validPeriods[i];
|
||||
if (period === outputPeriodLabel) { validPeriod = true }
|
||||
if (period === timePeriodLabel) { validPeriod = true }
|
||||
}
|
||||
if (validPeriod === false) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriods -> periodsLoopBody -> Discarding period for not being listed as a valid period. -> outputPeriodLabel = " + outputPeriodLabel); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriods -> periodsLoopBody -> Discarding period for not being listed as a valid period. -> timePeriodLabel = " + timePeriodLabel); }
|
||||
periodsControlLoop();
|
||||
return;
|
||||
}
|
||||
@@ -418,7 +418,7 @@
|
||||
let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.dataSet === "Multi-Period-Daily") {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + outputPeriodLabel + "/" + dateForPath;
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + timePeriodLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + dateForPath;
|
||||
}
|
||||
@@ -488,7 +488,7 @@
|
||||
let dateForPath = bot.multiPeriodDailyProcessDatetime.getUTCFullYear() + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.dataSet === "Multi-Period-Daily") {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + outputPeriodLabel + "/" + dateForPath;
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + timePeriodLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + dateForPath;
|
||||
}
|
||||
@@ -586,12 +586,12 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriods -> periodsLoopBody -> callTheBot -> Entering function."); }
|
||||
|
||||
const timePeriod = global.dailyFilePeriods[n][0];
|
||||
const outputPeriodLabel = global.dailyFilePeriods[n][1];
|
||||
const timePeriodLabel = global.dailyFilePeriods[n][1];
|
||||
|
||||
botInstance.start(
|
||||
dataFiles,
|
||||
timePeriod,
|
||||
outputPeriodLabel,
|
||||
timePeriodLabel,
|
||||
bot.multiPeriodDailyProcessDatetime,
|
||||
interExecutionMemoryArray[n],
|
||||
onBotFinished);
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriods -> periodsLoopBody -> Entering function."); }
|
||||
|
||||
const timePeriod = global.marketFilesPeriods[n][0];
|
||||
const outputPeriodLabel = global.marketFilesPeriods[n][1];
|
||||
const timePeriodLabel = global.marketFilesPeriods[n][1];
|
||||
|
||||
let dependencyIndex = 0;
|
||||
dataFiles = new Map;
|
||||
@@ -177,7 +177,7 @@
|
||||
|
||||
let filePath
|
||||
if (dependency.dataSet === "Multi-Period-Market") {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + outputPeriodLabel;
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + timePeriodLabel;
|
||||
} else {
|
||||
filePath = dependency.product + '/' + dependency.dataSet + "/" + dateForPath;
|
||||
}
|
||||
@@ -253,12 +253,12 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimePeriods -> periodsLoopBody -> callTheBot -> Entering function."); }
|
||||
|
||||
const timePeriod = global.marketFilesPeriods[n][0];
|
||||
const outputPeriodLabel = global.marketFilesPeriods[n][1];
|
||||
const timePeriodLabel = global.marketFilesPeriods[n][1];
|
||||
|
||||
botInstance.start(
|
||||
dataFiles,
|
||||
timePeriod,
|
||||
outputPeriodLabel,
|
||||
timePeriodLabel,
|
||||
undefined,
|
||||
undefined,
|
||||
onBotFinished);
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
<Compile Include="TradingBotProcessMainLoop.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="TradingSimulation.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="utils\logger.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
|
||||
+80
-59
@@ -18,6 +18,8 @@
|
||||
const COMMONS = require('./Commons.js');
|
||||
let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE);
|
||||
|
||||
let assistant;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function finalize() {
|
||||
@@ -27,7 +29,7 @@
|
||||
commons = undefined
|
||||
}
|
||||
|
||||
function initialize(callBackFunction) {
|
||||
function initialize(pAssistant, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -36,6 +38,7 @@
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
|
||||
|
||||
assistant = pAssistant;
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
@@ -44,7 +47,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function start(multiPeriodDataFiles, timePeriod, outputPeriodLabel, currentDay, interExecutionMemory, callBackFunction) {
|
||||
function start(multiPeriodDataFiles, timePeriod, timePeriodLabel, currentDay, interExecutionMemory, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -100,73 +103,91 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* During the third phase, we need to generate the data of the different products this process produces an output */
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
const TRADING_SIMULATION = require('./TradingSimulation.js');
|
||||
let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES);
|
||||
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let jsonData // Just build data as Json objects
|
||||
let outputData // Data built as a result of applying user defined code and formulas at the Data Building Procedure
|
||||
let singularVariableName // Name of the variable for this product
|
||||
let recordDefinition // Record as defined by the user at the UI
|
||||
let fileContent // Here we store the contents of the new data built just before writing it to a file.
|
||||
let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution.
|
||||
tradingSimulation.runSimulation(
|
||||
chart,
|
||||
dataDependencies,
|
||||
timePeriod,
|
||||
timePeriodLabel,
|
||||
currentDay,
|
||||
interExecutionMemory,
|
||||
assistant,
|
||||
onSimulationFinished,
|
||||
callBackFunction)
|
||||
|
||||
/*
|
||||
For each outputDatasetNode in our process output, we will build the information based on our input products.
|
||||
*/
|
||||
|
||||
recordDefinition = outputDatasetNode.referenceParent.parentNode.record
|
||||
singularVariableName = outputDatasetNode.referenceParent.parentNode.code.singularVariableName
|
||||
function onSimulationFinished() {
|
||||
console.log("Finished")
|
||||
|
||||
/* Check Irregular Periods */
|
||||
if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.code.code.indexOf('results.push') >= 0) {
|
||||
resultsWithIrregularPeriods = true
|
||||
/* During the third phase, we need to generate the data of the different products this process produces an output */
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let jsonData // Just build data as Json objects
|
||||
let outputData // Data built as a result of applying user defined code and formulas at the Data Building Procedure
|
||||
let singularVariableName // Name of the variable for this product
|
||||
let recordDefinition // Record as defined by the user at the UI
|
||||
let fileContent // Here we store the contents of the new data built just before writing it to a file.
|
||||
let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution.
|
||||
|
||||
/*
|
||||
For each outputDatasetNode in our process output, we will build the information based on our input products.
|
||||
*/
|
||||
|
||||
recordDefinition = outputDatasetNode.referenceParent.parentNode.record
|
||||
singularVariableName = outputDatasetNode.referenceParent.parentNode.code.singularVariableName
|
||||
|
||||
/* Check Irregular Periods */
|
||||
if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.code.code.indexOf('results.push') >= 0) {
|
||||
resultsWithIrregularPeriods = true
|
||||
}
|
||||
|
||||
/* Build the data */
|
||||
jsonData = commons.dataBuildingProcedure(products, mainDependency, recordDefinition, outputDatasetNode.referenceParent.parentNode.dataBuilding, singularVariableName, timePeriod, resultsWithIrregularPeriods)
|
||||
|
||||
/* Add the calculated properties */
|
||||
if (outputDatasetNode.referenceParent.parentNode.calculations !== undefined) {
|
||||
outputData = commons.calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timePeriod)
|
||||
} else {
|
||||
outputData = jsonData
|
||||
}
|
||||
products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName] = outputData
|
||||
}
|
||||
|
||||
/* Build the data */
|
||||
jsonData = commons.dataBuildingProcedure(products, mainDependency, recordDefinition, outputDatasetNode.referenceParent.parentNode.dataBuilding, singularVariableName, timePeriod, resultsWithIrregularPeriods)
|
||||
/*At the fourth and last phase, we will save the new information generated into files corresponding to each output outputDatasetNode.*/
|
||||
let totalFilesWritten = 0
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let outputData = products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName]
|
||||
let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution.
|
||||
let contextSummary = {}
|
||||
|
||||
/* Add the calculated properties */
|
||||
if (outputDatasetNode.referenceParent.parentNode.calculations !== undefined) {
|
||||
outputData = commons.calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timePeriod)
|
||||
} else {
|
||||
outputData = jsonData
|
||||
/* Check Irregular Periods */
|
||||
if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.code.code.indexOf('results.push') >= 0) {
|
||||
resultsWithIrregularPeriods = true
|
||||
}
|
||||
|
||||
/* Simplifying the access to basic info */
|
||||
|
||||
contextSummary.dataset = outputDatasetNode.referenceParent.code.codeName
|
||||
contextSummary.product = outputDatasetNode.referenceParent.parentNode.code.codeName
|
||||
contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName
|
||||
contextSummary.devTeam = outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName
|
||||
|
||||
/* This stuff is still hardcoded and unresolved. */
|
||||
contextSummary.botVersion = {
|
||||
"major": 1,
|
||||
"minor": 0
|
||||
}
|
||||
contextSummary.dataSetVersion = "dataSet.V1"
|
||||
|
||||
let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, callBackFunction)
|
||||
commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, timePeriodLabel, callBackFunction)
|
||||
}
|
||||
products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName] = outputData
|
||||
}
|
||||
|
||||
/*At the fourth and last phase, we will save the new information generated into files corresponding to each output outputDatasetNode.*/
|
||||
let totalFilesWritten = 0
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let outputData = products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName]
|
||||
let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution.
|
||||
let contextSummary = {}
|
||||
|
||||
/* Check Irregular Periods */
|
||||
if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.code.code.indexOf('results.push') >= 0) {
|
||||
resultsWithIrregularPeriods = true
|
||||
}
|
||||
|
||||
/* Simplifying the access to basic info */
|
||||
|
||||
contextSummary.dataset = outputDatasetNode.referenceParent.code.codeName
|
||||
contextSummary.product = outputDatasetNode.referenceParent.parentNode.code.codeName
|
||||
contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName
|
||||
contextSummary.devTeam = outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName
|
||||
|
||||
/* This stuff is still hardcoded and unresolved. */
|
||||
contextSummary.botVersion = {
|
||||
"major": 1,
|
||||
"minor": 0
|
||||
}
|
||||
contextSummary.dataSetVersion = "dataSet.V1"
|
||||
|
||||
let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, callBackFunction)
|
||||
commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, outputPeriodLabel, callBackFunction)
|
||||
}
|
||||
|
||||
|
||||
function anotherFileWritten() {
|
||||
totalFilesWritten++
|
||||
if (totalFilesWritten === outputDatasets.length) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user