diff --git a/BackendServers/WebServer/AppLoader.js b/BackendServers/WebServer/AppLoader.js index f038011dc..ad9f15f1f 100644 --- a/BackendServers/WebServer/AppLoader.js +++ b/BackendServers/WebServer/AppLoader.js @@ -54,7 +54,13 @@ function newAppLoader() { 'DesignSpace/Workspace/FunctionLibraries/WebhookFunctions.js', 'DesignSpace/Workspace/FunctionLibraries/DependenciesFilter.js', - 'Utilities/RoundedCornersBackground.js', + 'Utilities/CoordinateTransformations.js', + 'Utilities/DateRateTransformations.js', + 'Utilities/Download.js', + 'Utilities/DrawPrint.js', + 'Utilities/LoadSaveFrame.js', + 'Utilities/NodeConfig.js', + 'Utilities/HiriarchyMap.js', 'Panels/LayersPanel.js', 'Panels/UpDownButton.js', @@ -110,22 +116,27 @@ function newAppLoader() { 'EventsServerClient.js', - 'VideoRecorder.js', - 'Plotter.js', + 'Plotting/Plotter.js', + 'Plotting/NodesHighlights.js', + 'Plotting/NodesErrors.js', + 'Plotting/NodesValues.js', + 'Plotting/NodesStatus.js', + 'Plotting/NodesProgress.js', + 'Plotting/NodesRunning.js', + 'Plotting/NodesAnnouncements.js', + 'Plotting/RecordValues.js', + 'LegacyPlotter.js', 'PlotterPanel.js', + 'VideoRecorder.js', 'ProductStorage.js', - 'SplashScreen.js', 'Canvas.js', 'EventHandler.js', 'Frame.js', - 'Animation.js', - 'Container.js', - 'Utilities.js', 'PostLoader.js' ]) @@ -187,7 +198,7 @@ function newAppLoader() { 'Plotters/TradingEngines/Plotters-Trading-Simulation/TradingSimulation.js', 'Plotters/TradingEngines/Plotters-Trading-Simulation/TradingSimulationAssetBalancesPanel.js', 'Plotters/TradingEngines/Plotters-Trading-Simulation/TradingSimulationCurrentPositionPanel.js', - 'Plotters/TradingEngines/Plotters-Trading-Simulation/TradingSimulationPartialResultsPanel.js', + 'Plotters/TradingEngines/Plotters-Trading-Simulation/TradingSimulationEpisodePanel.js', 'Plotters/TradingEngines/Plotters-Trading-Simulation/Conditions.js', 'Plotters/TradingEngines/Plotters-Trading-Simulation/Strategies.js', 'Plotters/TradingEngines/Plotters-Trading-Simulation/Trades.js', diff --git a/BackendServers/WebServer/Images/Icons/style-01/cancel-order.png b/BackendServers/WebServer/Images/Icons/style-01/cancel-order.png new file mode 100644 index 000000000..4d7b9dfa6 Binary files /dev/null and b/BackendServers/WebServer/Images/Icons/style-01/cancel-order.png differ diff --git a/BackendServers/WebServer/Images/Icons/style-01/execution-algorithm.png b/BackendServers/WebServer/Images/Icons/style-01/execution-algorithm.png new file mode 100644 index 000000000..66c3b88e6 Binary files /dev/null and b/BackendServers/WebServer/Images/Icons/style-01/execution-algorithm.png differ diff --git a/BackendServers/WebServer/Images/Icons/style-01/limit-order.png b/BackendServers/WebServer/Images/Icons/style-01/limit-order.png new file mode 100644 index 000000000..071808daf Binary files /dev/null and b/BackendServers/WebServer/Images/Icons/style-01/limit-order.png differ diff --git a/BackendServers/WebServer/Images/Icons/style-01/market-order.png b/BackendServers/WebServer/Images/Icons/style-01/market-order.png new file mode 100644 index 000000000..5ff1ee736 Binary files /dev/null and b/BackendServers/WebServer/Images/Icons/style-01/market-order.png differ diff --git a/BackendServers/WebServer/Images/Icons/style-01/move-order.png b/BackendServers/WebServer/Images/Icons/style-01/move-order.png new file mode 100644 index 000000000..6f1c26d1b Binary files /dev/null and b/BackendServers/WebServer/Images/Icons/style-01/move-order.png differ diff --git a/BackendServers/WebServer/Images/Icons/style-01/open-execution.png b/BackendServers/WebServer/Images/Icons/style-01/open-execution.png new file mode 100644 index 000000000..788edb635 Binary files /dev/null and b/BackendServers/WebServer/Images/Icons/style-01/open-execution.png differ diff --git a/BackendServers/WebServer/PlotterPanel.js b/BackendServers/WebServer/PlotterPanel.js index 75e63863e..40fad93d6 100644 --- a/BackendServers/WebServer/PlotterPanel.js +++ b/BackendServers/WebServer/PlotterPanel.js @@ -2,5 +2,10 @@ let functionName = pDataMineOrHost + pPlotter + pModule + pPanel; functionName = functionName.replace(/-/g, ""); functionName = 'new' + functionName - return window[functionName](); + try { + return window[functionName](); + } catch (err) { + console.log('[ERROR] getNewPlotterPanel -> Error trying to Load a Plotter Panel. err = ' + err.stack) + console.log('[ERROR] getNewPlotterPanel -> functionName = ' + functionName) + } } diff --git a/BackendServers/server.js b/BackendServers/server.js index 4a9cf0c44..7878b134f 100644 --- a/BackendServers/server.js +++ b/BackendServers/server.js @@ -65,6 +65,8 @@ try { WEB_SERVER.initialize() WEB_SERVER.run() + console.log("You are running Superalgos Backend Beta 6.") + } catch (err) { console.log('[ERROR] BackendServers -> Task Manager -> server -> Error = ' + err.stack) } diff --git a/BackendServers/webServer.js b/BackendServers/webServer.js index a09892e33..dabd67ed9 100644 --- a/BackendServers/webServer.js +++ b/BackendServers/webServer.js @@ -325,6 +325,12 @@ exports.newWebServer = function newWebServer(EVENTS_SERVER) { } break + case 'Plotting': // This means the Plotting folder. + { + respondWithFile(process.env.PATH_TO_CANVAS_APP + '/Plotting/' + requestParameters[2], response) + } + break + case 'TopSpace': // This means the TopSpace folder. { respondWithFile(process.env.PATH_TO_CANVAS_APP + '/TopSpace/' + requestParameters[2], response) @@ -584,46 +590,6 @@ exports.newWebServer = function newWebServer(EVENTS_SERVER) { } break - case 'GetDefinition': - { - respondWithFile(process.env.INTER_PROCESS_FILES_PATH + '/definition.json', response) - } - break - - case 'SaveDefinition': - { - let fs = require('fs') - let body = '' - - request.on('data', function (data) { - body += data - // Too much POST data - if (body.length > 1e6) { - request.connection.destroy() - } - }) - - request.on('end', function () { - try { - let filePath = process.env.INTER_PROCESS_FILES_PATH + '/definition.json' - fs.writeFile(filePath, body, onFileWrite) - } catch (e) { - console.log('[ERROR] Error writing user config.', e) - respondWithContent(undefined, response) - } - - function onFileWrite(err) { - if (err) { - respondWithContent(undefined, response) - } else { - let responseContent = 'User config updated.' - respondWithContent(responseContent, response) - } - } - }) - } - break - default: { homePage() diff --git a/CanvasApp/ChartingSpace/PlottersManager.js b/CanvasApp/ChartingSpace/PlottersManager.js index a0002b6fa..3b76c7698 100644 --- a/CanvasApp/ChartingSpace/PlottersManager.js +++ b/CanvasApp/ChartingSpace/PlottersManager.js @@ -99,7 +99,7 @@ function newPlottersManager () { for (let i = 0; i < layer.definition.referenceParent.referenceParent.datasets.length; i++) { let dataset = layer.definition.referenceParent.referenceParent.datasets[i] - switch (dataset.code.type) { + switch (dataset.config.type) { case 'Market Files': { storage.onMarketFileLoadedLayerEventsSubscriptionId = storage.eventHandler.listenToEvent('Market File Loaded', layer.onMarketFileLoaded) } @@ -125,29 +125,43 @@ function newPlottersManager () { } } - let baseAsset = layer.definition.referenceParent.parentNode.referenceParent.baseAsset.referenceParent.code.codeName - let quotedAsset = layer.definition.referenceParent.parentNode.referenceParent.quotedAsset.referenceParent.code.codeName + let baseAsset = layer.definition.referenceParent.parentNode.referenceParent.baseAsset.referenceParent.config.codeName + let quotedAsset = layer.definition.referenceParent.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName let market = { baseAsset: baseAsset, quotedAsset: quotedAsset } - let product = layer.definition.referenceParent.referenceParent + let productDefinition = layer.definition.referenceParent.referenceParent let bot = layer.definition.referenceParent.referenceParent.parentNode let dataMine = layer.definition.referenceParent.referenceParent.parentNode.parentNode let exchange = layer.definition.referenceParent.parentNode.referenceParent.parentNode.parentNode let plotterModule = layer.definition.referenceParent.referenceParent.referenceParent let session + let tradingSystem + let tradingEngine + /* + A layer can be referencing a Data Product in two different branches of the Network hiriatchy. + In one of those braches we can get the Session node, at the other we Cannot. + */ if (layer.definition.referenceParent.parentNode.parentNode !== undefined) { if (layer.definition.referenceParent.parentNode.parentNode.type === 'Session Reference') { if (layer.definition.referenceParent.parentNode.parentNode.referenceParent !== undefined) { session = layer.definition.referenceParent.parentNode.parentNode.referenceParent + + /* From the session we might be able to reach the Trading System or the Trading Engine */ + if (session.tradingSystemReference !== undefined) { + tradingSystem = session.tradingSystemReference.referenceParent + } + if (session.tradingEngineReference !== undefined) { + tradingEngine = session.tradingEngineReference.referenceParent + } } } } - let host = layer.networkNode.code.host - let webPort = layer.networkNode.code.webPort + let host = layer.networkNode.config.host + let webPort = layer.networkNode.config.webPort if (host === undefined) { host = 'localhost' } if (webPort === undefined) { webPort = window.location.port } @@ -157,7 +171,7 @@ function newPlottersManager () { dataMine, bot, session, - product, + productDefinition, exchange, market, datetime, @@ -169,66 +183,154 @@ function newPlottersManager () { ) function onProductStorageInitialized (err) { - try { - if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { /* Now we have all the initial data loaded and ready to be delivered to the new instance of the plotter. */ - let plotter + let plotter - if (plotterModule.code.isLegacy !== true) { - plotter = newPlotter() - } else { - plotter = getNewPlotter(dataMine.code.codeName, plotterModule.parentNode.code.codeName, plotterModule.code.codeName) + if (plotterModule.config.isLegacy !== true) { + plotter = newPlotter() + } else { + plotter = getNewPlotter(dataMine.config.codeName, plotterModule.parentNode.config.codeName, plotterModule.config.codeName) + } + + plotter.container.connectToParent(thisObject.container, true, true, false, true, true, true, false, false, true) + plotter.container.frame.position.x = thisObject.container.frame.width / 2 - plotter.container.frame.width / 2 + plotter.container.frame.position.y = thisObject.container.frame.height / 2 - plotter.container.frame.height / 2 + plotter.fitFunction = thisObject.fitFunction + plotter.initialize(storage, datetime, timeFrame, coordinateSystem, onPlotterInizialized, productDefinition) + + function onPlotterInizialized () { + let connector = { + layer: layer, + plotter: plotter, + storage: storage } + /* Let the Plotter listen to the event of Cursor Files loaded, so that it can react recalculating if needed. */ + plotter.onDailyFileLoadedPlotterEventsSubscriptionId = storage.eventHandler.listenToEvent('Daily File Loaded', plotter.onDailyFileLoaded) + /* Lets load now this plotter panels. */ + connector.panels = [] - plotter.container.connectToParent(thisObject.container, true, true, false, true, true, true, false, false, true) - plotter.container.frame.position.x = thisObject.container.frame.width / 2 - plotter.container.frame.width / 2 - plotter.container.frame.position.y = thisObject.container.frame.height / 2 - plotter.container.frame.height / 2 - plotter.fitFunction = thisObject.fitFunction - plotter.initialize(storage, datetime, timeFrame, coordinateSystem, onPlotterInizialized, product) + if (productDefinition !== undefined) { + /* Here we setup the panels associated with this plotter */ + for (let i = 0; i < plotterModule.panels.length; i++) { + let panel = plotterModule.panels[i] - function onPlotterInizialized () { - try { - let connector = { - layer: layer, - plotter: plotter, - storage: storage - } - /* Let the Plotter listen to the event of Cursor Files loaded, so that it can react recalculating if needed. */ - plotter.onDailyFileLoadedPlotterEventsSubscriptionId = storage.eventHandler.listenToEvent('Daily File Loaded', plotter.onDailyFileLoaded) - /* Lets load now this plotter panels. */ - connector.panels = [] - - if (product !== undefined) { - for (let i = 0; i < plotterModule.panels.length; i++) { - let panel = plotterModule.panels[i] - - let parameters = { - dataMine: dataMine.code.codeName, - plotterCodeName: plotterModule.parentNode.code.codeName, - moduleCodeName: plotterModule.code.codeName, - panelNode: panel - } - - let owner = thisObject.payload.node.payload.parentNode.id // Panels are owned by the time machine. - let plotterPanelHandle = canvas.panelsSpace.createNewPanel('Plotter Panel', parameters, owner, layer.session) - let plotterPanel = canvas.panelsSpace.getPanel(plotterPanelHandle) - - /* Connect Panel to the Plotter via an Event. */ - connector.plotter.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', plotterPanel.onRecordChange) - connector.panels.push(plotterPanelHandle) - } - - connector.layer.panels = connector.panels + let parameters = { + dataMine: dataMine.config.codeName, + plotterCodeName: plotterModule.parentNode.config.codeName, + moduleCodeName: plotterModule.config.codeName, + panelNode: panel } - thisObject.connectors.push(connector) - } catch (err) { - if (ERROR_LOG === true) { logger.write('[ERROR] initializePlotter -> onProductStorageInitialized -> onPlotterInizialized -> err = ' + err.stack) } + let owner = thisObject.payload.node.payload.parentNode.id // Panels are owned by the time machine. + let plotterPanelHandle = canvas.panelsSpace.createNewPanel('Plotter Panel', parameters, owner, layer.session) + let plotterPanel = canvas.panelsSpace.getPanel(plotterPanelHandle) + + /* Connect Panel to the Plotter via an Event. */ + connector.plotter.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', plotterPanel.onRecordChange) + connector.panels.push(plotterPanelHandle) + } + connector.layer.panels = connector.panels + + /* + Next we will check the different types of Plotting related to connecting to an existing node branch. + */ + if (plotterModule.nodesHighlights !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesHighlights = newNodesHighlights() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesHighlights.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesHighlights.initialize(tradingEngine) + break + } + connector.nodesHighlights.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesHighlights.onRecordChange) + } + if (plotterModule.nodesValues !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesValues = newNodesValues() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesValues.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesValues.initialize(tradingEngine) + break + } + connector.nodesValues.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesValues.onRecordChange) + } + if (plotterModule.nodesErrors !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesErrors = newNodesErrors() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesErrors.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesErrors.initialize(tradingEngine) + break + } + connector.nodesErrors.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesErrors.onRecordChange) + } + if (plotterModule.nodesRunning !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesRunning = newNodesRunning() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesRunning.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesRunning.initialize(tradingEngine) + break + } + connector.nodesRunning.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesRunning.onRecordChange) + } + if (plotterModule.nodesStatus !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesStatus = newNodesStatus() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesStatus.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesStatus.initialize(tradingEngine) + break + } + connector.nodesStatus.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesStatus.onRecordChange) + } + if (plotterModule.nodesProgress !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesProgress = newNodesProgress() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesProgress.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesProgress.initialize(tradingEngine) + break + } + connector.nodesProgress.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesProgress.onRecordChange) + } + if (plotterModule.nodesAnnouncements !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesAnnounements = newNodesAnnouncements() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesAnnounements.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesAnnounements.initialize(tradingEngine) + break + } + connector.nodesAnnounements.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesAnnounements.onRecordChange) + } + /* + Another type of plotting is with Record Values. This method uses the record definition to find the target node + and from there is looks into its children all based on the Record Definition configuration. + */ + if (plotterModule.recordValues !== undefined) { + connector.recordValues = newRecordValues() + connector.recordValues.initialize(tradingSystem, tradingEngine, productDefinition) + connector.recordValues.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.recordValues.onRecordChange) } } + thisObject.connectors.push(connector) } - } catch (err) { - if (ERROR_LOG === true) { logger.write('[ERROR] initializePlotter -> onProductStorageInitialized -> err = ' + err.stack) } } } } catch (err) { @@ -262,8 +364,8 @@ function newPlottersManager () { for (let j = 0; j < connector.panels.length; j++) { canvas.panelsSpace.destroyPanel(connector.panels[j]) } - /* Finally the Storage Objects */ - + /* Finally the Storage Objects */ + finalizeNodesPlotting(connector) if (connector.plotter.finalize !== undefined) { connector.plotter.container.eventHandler.stopListening(connector.plotter.onRecordChangeEventsSubscriptionId) connector.plotter.container.finalize() @@ -277,6 +379,37 @@ function newPlottersManager () { } } + function finalizeNodesPlotting (connector) { + if (connector.nodesHighlights !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesHighlights.onRecordChangeEventsSubscriptionId) + connector.nodesHighlights.finalize() + } + if (connector.nodesValues !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesValues.onRecordChangeEventsSubscriptionId) + connector.nodesValues.finalize() + } + if (connector.nodesErrors !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesErrors.onRecordChangeEventsSubscriptionId) + connector.nodesErrors.finalize() + } + if (connector.nodesStatus !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesStatus.onRecordChangeEventsSubscriptionId) + connector.nodesStatus.finalize() + } + if (connector.nodesProgress !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.Progress.onRecordChangeEventsSubscriptionId) + connector.Progress.finalize() + } + if (connector.nodesRunning !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesRunning.onRecordChangeEventsSubscriptionId) + connector.nodesRunning.finalize() + } + if (connector.recordValues !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.recordValues.onRecordChangeEventsSubscriptionId) + connector.recordValues.finalize() + } + } + function setTimeFrame (pTimeFrame) { timeFrame = pTimeFrame for (let i = 0; i < thisObject.connectors.length; i++) { diff --git a/CanvasApp/ChartingSpace/Viewport.js b/CanvasApp/ChartingSpace/Viewport.js index 4f60172be..5bd837de8 100644 --- a/CanvasApp/ChartingSpace/Viewport.js +++ b/CanvasApp/ChartingSpace/Viewport.js @@ -369,10 +369,10 @@ function newViewport () { /* Save the zoom at the node config, so that the user can change it if he wishes to. */ try { - let code = JSON.parse(thisObject.payload.node.code) - code.zoom = (thisObject.zoomTargetLevel - MIN_ZOOM_LEVEL) / (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) * 100 - code.zoom = code.zoom.toFixed(2) - thisObject.payload.node.code = JSON.stringify(code) + let config = JSON.parse(thisObject.payload.node.config) + config.zoom = (thisObject.zoomTargetLevel - MIN_ZOOM_LEVEL) / (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) * 100 + config.zoom = config.zoom.toFixed(2) + thisObject.payload.node.config = JSON.stringify(config) } catch (err) { // we ignore errors here since most likely they will be parsing errors. } @@ -383,18 +383,18 @@ function newViewport () { /* Read the zoom level from the node config */ try { - let code = JSON.parse(thisObject.payload.node.code) + let config = JSON.parse(thisObject.payload.node.config) - if (isNaN(code.zoom) || code.zoom === null || code.zoom === undefined) { + if (isNaN(config.zoom) || config.zoom === null || config.zoom === undefined) { saveObjectState() return } - code.zoom = code.zoom / 100 * (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) + MIN_ZOOM_LEVEL - if (code.zoom < MIN_ZOOM_LEVEL) { code.zoom = MIN_ZOOM_LEVEL } - if (code.zoom > MAX_ZOOM_LEVEL) { code.zoom = MAX_ZOOM_LEVEL } + config.zoom = config.zoom / 100 * (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) + MIN_ZOOM_LEVEL + if (config.zoom < MIN_ZOOM_LEVEL) { config.zoom = MIN_ZOOM_LEVEL } + if (config.zoom > MAX_ZOOM_LEVEL) { config.zoom = MAX_ZOOM_LEVEL } - if (code.zoom.toFixed(2) !== thisObject.zoomTargetLevel.toFixed(2)) { - newZoomLevel(code.zoom) + if (config.zoom.toFixed(2) !== thisObject.zoomTargetLevel.toFixed(2)) { + newZoomLevel(config.zoom) } else { saveObjectState() } diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/CCXTFunctions.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/CCXTFunctions.js index e89b15e3b..047c4dcf3 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/CCXTFunctions.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/CCXTFunctions.js @@ -44,7 +44,7 @@ function newCCXTFunctions () { if (existingExchange === undefined) { let newExchange = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Crypto Exchange') newExchange.name = exchange.name - newExchange.code = '{ \n\"codeName\": \"' + exchange.id + '\"\n}' + newExchange.config = '{ \n\"codeName\": \"' + exchange.id + '\"\n}' newExchange.payload.floatingObject.collapseToggle() newExchange.exchangeAssets.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 newExchange.exchangeMarkets.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 @@ -114,7 +114,7 @@ function newCCXTFunctions () { function addAsset (name) { let newAsseet = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Asset') newAsseet.name = name - newAsseet.code = '{ \n\"codeName\": \"' + name + '\"\n}' + newAsseet.config = '{ \n\"codeName\": \"' + name + '\"\n}' } } } @@ -182,7 +182,7 @@ function newCCXTFunctions () { function addMarket (name, baseAsset, quotedAsset) { let newMarket = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Market') newMarket.name = name - newMarket.code = '{ \n\"codeName\": \"' + name + '\"\n}' + newMarket.config = '{ \n\"codeName\": \"' + name + '\"\n}' newMarket.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 newMarket.payload.floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_050X newMarket.payload.floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ChainAttachDetach.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ChainAttachDetach.js index 44dd1e1db..5a75546ab 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ChainAttachDetach.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ChainAttachDetach.js @@ -31,11 +31,11 @@ function newChainAttachDetach () { return } default: { - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition (node) if (nodeDefinition !== undefined) { /* Detach from parent */ if (node.payload.parentNode !== undefined) { - let parentNodeDefinition = APP_SCHEMA_MAP.get(node.payload.parentNode.type) + let parentNodeDefinition = getNodeDefinition (node.payload.parentNode) if (parentNodeDefinition !== undefined) { if (parentNodeDefinition.properties !== undefined) { for (let i = 0; i < parentNodeDefinition.properties.length; i++) { @@ -166,13 +166,13 @@ function newChainAttachDetach () { } break default: { - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition (node) if (nodeDefinition !== undefined) { node.payload.parentNode = attachToNode node.payload.chainParent = attachToNode /* Attach to new parent */ if (node.payload.parentNode !== undefined) { - let parentNodeDefinition = APP_SCHEMA_MAP.get(node.payload.parentNode.type) + let parentNodeDefinition = getNodeDefinition (node.payload.parentNode) if (parentNodeDefinition !== undefined) { if (parentNodeDefinition.properties !== undefined) { for (let i = 0; i < parentNodeDefinition.properties.length; i++) { diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/DependenciesFilter.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/DependenciesFilter.js index 2f88bf30d..1e4fb26ac 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/DependenciesFilter.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/DependenciesFilter.js @@ -14,7 +14,7 @@ function newDependenciesFilter () { function recursiveFilter (node) { filter(node.code) - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition (node) if (nodeDefinition !== undefined) { if (nodeDefinition.properties !== undefined) { let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeChildren.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeChildren.js index 894dd0623..0165b1ecd 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeChildren.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeChildren.js @@ -14,7 +14,7 @@ function newNodeChildren () { childrenCount: 0, childIndex: undefined } - let parentNodeDefinition = APP_SCHEMA_MAP.get(parentNode.type) + let parentNodeDefinition = getNodeDefinition (parentNode) if (parentNodeDefinition !== undefined) { if (parentNodeDefinition.properties !== undefined) { let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. @@ -34,7 +34,7 @@ function newNodeChildren () { } break case 'array': { - let nodeDefinition = APP_SCHEMA_MAP.get(childNode.type) + let nodeDefinition = getNodeDefinition (childNode) if (nodeDefinition !== undefined) { let nodePropertyArray = parentNode[property.name] for (let j = 0; j < nodePropertyArray.length; j++) { diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeCloning.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeCloning.js index fce49b44f..6de8afad8 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeCloning.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeCloning.js @@ -19,12 +19,13 @@ function newNodeCloning () { function cloneNode (node) { if (node === undefined) { return } - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition(node) if (nodeDefinition !== undefined) { let object = { type: node.type, name: node.name, - code: node.code + code: node.code, + config: node.config } /* Children Nodes */ @@ -71,12 +72,13 @@ function newNodeCloning () { function redirectReferenceParents (node) { if (node === undefined) { return } - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition(node) if (nodeDefinition !== undefined) { let object = { type: node.type, name: node.name, - code: node.code + code: node.code, + config: node.config } /* Children Nodes */ diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeDeleter.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeDeleter.js index c880ade46..7b17905dc 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeDeleter.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/NodeDeleter.js @@ -29,7 +29,7 @@ function newNodeDeleter () { node.payload.referenceParent.payload.uiObject.isShowing = false } - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition (node) if (nodeDefinition !== undefined) { /* Remove all of its own children nodes. */ if (nodeDefinition.properties !== undefined) { @@ -63,7 +63,7 @@ function newNodeDeleter () { /* Remove node from parent */ if (node.payload !== undefined) { if (node.payload.parentNode !== undefined) { - let parentNodeDefinition = APP_SCHEMA_MAP.get(node.payload.parentNode.type) + let parentNodeDefinition = getNodeDefinition (node.payload.parentNode) if (parentNodeDefinition !== undefined) { if (parentNodeDefinition.properties !== undefined) { for (let i = 0; i < parentNodeDefinition.properties.length; i++) { diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/OnFocus.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/OnFocus.js index a45c9f76c..21596fb7c 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/OnFocus.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/OnFocus.js @@ -7,7 +7,7 @@ function newOnFocus () { function getNodeThatIsOnFocus (node) { if (node === undefined) { return } if (node.payload === undefined) { return } - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition (node) if (nodeDefinition !== undefined) { /* First we ask the question to ourself */ if (node.payload.uiObject.isOnFocus === true) { diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ProtocolNode.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ProtocolNode.js index 73d626530..a6a533549 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ProtocolNode.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ProtocolNode.js @@ -9,17 +9,18 @@ function newProtocolNode () { } return thisObject - function getProtocolNode (node, removePersonalData, parseCode, includeIds, includePayload, includeReferencesInSavedPayload, lightingPath) { + function getProtocolNode (node, removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, lightingPath) { if (node === undefined) { return } if (node.payload === undefined) { return } - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition(node) if (nodeDefinition !== undefined) { if (removePersonalData === true && nodeDefinition.isPersonalData === true) { return } let object = { type: node.type, name: node.name, - code: node.code + code: node.code, + config: node.config } /* Children Nodes */ @@ -31,11 +32,11 @@ function newProtocolNode () { case 'node': { if (node[property.name] !== undefined) { if (lightingPath === undefined) { - object[property.name] = getProtocolNode(node[property.name], removePersonalData, parseCode, includeIds, includeReferencesInSavedPayload, includePayload) + object[property.name] = getProtocolNode(node[property.name], removePersonalData, parseConfig, includeIds, includeReferencesInSavedPayload, includePayload) } else { let newLightingPath = getNewLightingPath(lightingPath, node.type, property.childType) if (newLightingPath !== undefined) { - object[property.name] = getProtocolNode(node[property.name], removePersonalData, parseCode, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + object[property.name] = getProtocolNode(node[property.name], removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) } } } @@ -47,7 +48,7 @@ function newProtocolNode () { let nodePropertyArray = node[property.name] object[property.name] = [] for (let m = 0; m < nodePropertyArray.length; m++) { - let protocolNode = getProtocolNode(nodePropertyArray[m], removePersonalData, parseCode, includeIds, includeReferencesInSavedPayload, includePayload) + let protocolNode = getProtocolNode(nodePropertyArray[m], removePersonalData, parseConfig, includeIds, includeReferencesInSavedPayload, includePayload) if (protocolNode !== undefined) { object[property.name].push(protocolNode) } @@ -58,7 +59,7 @@ function newProtocolNode () { let nodePropertyArray = node[property.name] object[property.name] = [] for (let m = 0; m < nodePropertyArray.length; m++) { - let protocolNode = getProtocolNode(nodePropertyArray[m], removePersonalData, parseCode, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + let protocolNode = getProtocolNode(nodePropertyArray[m], removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) if (protocolNode !== undefined) { object[property.name].push(protocolNode) } @@ -76,25 +77,25 @@ function newProtocolNode () { if (node.payload.parentNode !== undefined) { let newLightingPath = getNewLightingPath(lightingPath, node.type, node.payload.parentNode.type) if (newLightingPath !== undefined) { - object.parentNode = getProtocolNode(node.payload.parentNode, removePersonalData, parseCode, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + object.parentNode = getProtocolNode(node.payload.parentNode, removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) } } if (node.payload.referenceParent !== undefined) { let newLightingPath = getNewLightingPath(lightingPath, node.type, node.payload.referenceParent.type) if (newLightingPath !== undefined) { - object.referenceParent = getProtocolNode(node.payload.referenceParent, removePersonalData, parseCode, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + object.referenceParent = getProtocolNode(node.payload.referenceParent, removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) } } - if (parseCode && object.code !== undefined && nodeDefinition.editors !== undefined) { + if (parseConfig && object.config !== undefined && nodeDefinition.editors !== undefined) { if (nodeDefinition.editors.config === true) { try { - object.code = JSON.parse(node.code) + object.config = JSON.parse(node.config) } catch (err) { if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> err = ' + err.stack) } if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.type = ' + node.type) } if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.name = ' + node.name) } - if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.code = ' + node.code) } + if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.config = ' + node.config) } if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.id = ' + node.id) } } } diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SessionFunctions.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SessionFunctions.js index e1f7883e2..8d3f50c2a 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SessionFunctions.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SessionFunctions.js @@ -6,7 +6,7 @@ function newSessionFunctions () { return thisObject - function runSession (node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, callBackFunction) { + function runSession (node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, resume, callBackFunction) { if (validations(node) !== true) { callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) return @@ -24,37 +24,66 @@ function newSessionFunctions () { 'Parameters->' + 'Base Asset->Quoted Asset->Time Range->Time Frame->Slippage->Fee Structure->' + 'Exchange Account Asset->Asset->' + - 'Strategy->' + + 'Trading Strategy->' + 'Trigger Stage->Trigger On Event->Trigger Off Event->Take Position Event->' + 'Announcement->Formula->' + 'Open Stage->Initial Definition->Open Execution->' + 'Position Size->Position Rate->Formula->' + 'Initial Stop->Initial Take Profit->' + 'Manage Stage->' + - 'Stop->Take Profit->' + - 'Phase->Formula->Next Phase Event->Move to Phase Event->Phase->' + + 'Managed Stop Loss->Managed Take Profit->' + + 'Phase->Formula->Next Phase Event->Move To Phase Event->Phase->' + 'Situation->Condition->Javascript Code->' + 'Close Stage->Close Execution->' + 'Announcement->Formula->' - let tradingSystem = functionLibraryProtocolNode.getProtocolNode(node.payload.referenceParent, false, true, true, false, false, lightingPath) + let tradingSystem = functionLibraryProtocolNode.getProtocolNode(node.tradingSystemReference.payload.referenceParent, false, true, true, false, false, lightingPath) + + lightingPath = '' + + 'Trading Engine->' + + 'Dynamic Indicators->Indicator Function->Formula->' + + 'Episode->Current->Last->Previous->' + + 'Episode Counters->Episode Statistics->' + + 'Periods->Positions->Hits->Fails->' + + 'Profit Loss->Hit Ratio->Days->ROI->Anualized Rate Of Return->User Defined Statistic->' + + 'Formula->' + + 'Candle->' + + 'Begin->End->Open->Close->Min->Max->Index->' + + 'Balance->Base Asset->Quoted Asset->' + + 'Distance To Event->' + + 'Trigger On->Trigger Off->Take Position->Close Position->Next Phase->Move To Phase->' + + 'Strategy->' + + 'Begin->End->Strategy Name->Index->Stage Type->Status->Begin Rate->End Rate->Situation Name->' + + 'Strategy Counters->Periods->' + + 'Position->' + + 'Begin->End->Rate->Size->Exit Type->Status->Begin Rate->End Rate->Situation Name->' + + 'Stop Loss->Stop Loss Stage->Stop Loss Phase->' + + 'Take Profit->Take Profit Stage->Take Profit Phase->' + + 'Position Counters->Periods->' + + 'Position Statistics->Profit Loss->Days->ROI->Hit Fail->User Defined Statistic->' + + 'Formula' + + let tradingEngine = functionLibraryProtocolNode.getProtocolNode(node.tradingEngineReference.payload.referenceParent, false, true, true, false, false, lightingPath) lightingPath = '' + 'Backtesting Session->Paper Trading Session->Fordward Testing Session->Live Trading Session->' + 'Parameters->' + - 'Base Asset->Quoted Asset->Time Range->Time Frame->Slippage->Fee Structure->' + + 'Session Base Asset->Session Quoted Asset->Time Range->Time Frame->Slippage->Fee Structure->Snapshots->User Defined Parameters->' + 'Exchange Account Asset->Asset->' + 'Social Bots->Telegram Bot->' let session = functionLibraryProtocolNode.getProtocolNode(node, false, true, true, false, false, lightingPath) - let dependencyFilter = functionLibraryDependenciesFilter.createFilter(node.payload.referenceParent) + let dependencyFilter = functionLibraryDependenciesFilter.createFilter(node.tradingSystemReference.payload.referenceParent) /* Raise event to run the session */ let event = { + appSchema: JSON.stringify(APP_SCHEMA_ARRAY), session: JSON.stringify(session), tradingSystem: JSON.stringify(tradingSystem), - dependencyFilter: JSON.stringify(dependencyFilter) + tradingEngine: JSON.stringify(tradingEngine), + dependencyFilter: JSON.stringify(dependencyFilter), + resume: resume } eventsServerClient.raiseEvent(key, 'Run Session', event) @@ -121,8 +150,23 @@ function newSessionFunctions () { return } - if (node.payload.referenceParent === undefined) { - node.payload.uiObject.setErrorMessage('Session needs to reference a Trading System.') + if (node.tradingSystemReference === undefined) { + node.payload.uiObject.setErrorMessage('Session needs a child Trading System Reference.') + return + } + + if (node.tradingEngineReference === undefined) { + node.payload.uiObject.setErrorMessage('Session needs a child Trading Engine Reference.') + return + } + + if (node.tradingSystemReference.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('Trading System Reference needs to reference a Trading System.') + return + } + + if (node.tradingEngineReference.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('Trading Engine Reference needs to reference a Trading Engine.') return } return true diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ShortcutKeys.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ShortcutKeys.js index 28e6afc2a..2cd449769 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ShortcutKeys.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/ShortcutKeys.js @@ -6,7 +6,7 @@ function newShortcutKeys () { function getNodeByShortcutKey (node, searchingKey) { if (node === undefined) { return } - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition (node) if (nodeDefinition !== undefined) { /* Check Self first */ if (node.payload.uiObject.shortcutKey === searchingKey) { diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SuperScripts.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SuperScripts.js index 3965fe328..e8e6b8a90 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SuperScripts.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/SuperScripts.js @@ -18,7 +18,7 @@ function newSuperScriptsFunctions () { if (exchange === undefined) { functionLibraryUiObjectsFromNodes.createUiObjectFromNode(templateClone, target, target) } else { - let definition = APP_SCHEMA_MAP.get(target.type) + let definition = getNodeDefinition (target) for (let i = 0; i < definition.properties.length; i++) { let property = definition.properties[i] if (property.childType === exchangeNodeType) { diff --git a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/UiObjectsFromNodes.js b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/UiObjectsFromNodes.js index b29c96755..c852cb93f 100644 --- a/CanvasApp/DesignSpace/Workspace/FunctionLibraries/UiObjectsFromNodes.js +++ b/CanvasApp/DesignSpace/Workspace/FunctionLibraries/UiObjectsFromNodes.js @@ -26,14 +26,14 @@ function newUiObjectsFromNodes () { function addIncludedNodes () { let blobService = newFileStorage() - // if (node.code === undefined) { - node.code = '{ \n"includeDataMines": ["Masters", "Sparta", "TradingEngines"],\n"includeTradingSystems": ["Sparta-WHB-BTC-USDT", "Example-ETH-USDT", "Sparta-BRR-BTC-USDT"],\n"includeSuperScripts": ["Masters"]\n }' + // if (node.config === undefined) { + node.config = '{ \n"includeDataMines": ["Masters", "Sparta", "Masters-Trading"],\n"includeTradingSystems": ["Sparta-WHB-BTC-USDT", "Example-ETH-USDT", "Sparta-BRR-BTC-USDT"],\n"includeSuperScripts": ["Masters"]\n }' // } - let code = JSON.parse(node.code) - let includeDataMines = code.includeDataMines - let includeTradingSystems = code.includeTradingSystems - let includeSuperScripts = code.includeSuperScripts + let config = JSON.parse(node.config) + let includeDataMines = config.includeDataMines + let includeTradingSystems = config.includeTradingSystems + let includeSuperScripts = config.includeSuperScripts let totalIncluded = 0 @@ -49,9 +49,9 @@ function newUiObjectsFromNodes () { for (let i = 0; i < node.rootNodes.length; i++) { let rootNode = node.rootNodes[i] if (rootNode.type === 'Data Mine') { - if (rootNode.code !== undefined) { - let code = JSON.parse(rootNode.code) - if (code.name === name) { + if (rootNode.config !== undefined) { + let config = JSON.parse(rootNode.config) + if (config.name === name) { rootNodes.splice(i, 1) } } @@ -78,9 +78,9 @@ function newUiObjectsFromNodes () { for (let i = 0; i < node.rootNodes.length; i++) { let rootNode = node.rootNodes[i] if (rootNode.type === 'Trading System') { - if (rootNode.code !== undefined) { - let code = JSON.parse(rootNode.code) - if (code.name === name) { + if (rootNode.config !== undefined) { + let config = JSON.parse(rootNode.config) + if (config.name === name) { rootNodes.splice(i, 1) } } @@ -107,9 +107,9 @@ function newUiObjectsFromNodes () { for (let i = 0; i < node.rootNodes.length; i++) { let rootNode = node.rootNodes[i] if (rootNode.type === 'Super Scripts') { - if (rootNode.code !== undefined) { - let code = JSON.parse(rootNode.code) - if (code.name === name) { + if (rootNode.config !== undefined) { + let config = JSON.parse(rootNode.config) + if (config.name === name) { rootNodes.splice(i, 1) } } @@ -179,10 +179,33 @@ function newUiObjectsFromNodes () { } } + function migrateCodeToConfig (node, nodeDefinition) { + /* Code needed to Migrante from Beta 5 to Beta a Workspace */ + if (nodeDefinition.editors !== undefined) { + if (nodeDefinition.editors.config === true) { + if (node.code !== undefined) { + node.config = node.code + node.code = undefined + } + } + } + } + function createUiObjectFromNode (node, parentNode, chainParent, positionOffset) { + /* Code needed to Migrante from Beta 5 to Beta 6 a Trading System + if (node.type === 'Take Profit') { + node.type = 'Managed Take Profit' + } + if (node.type === 'Stop') { + node.type = 'Managed Stop Loss' + } + */ + /* Get node definition */ - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition(node) if (nodeDefinition !== undefined) { + migrateCodeToConfig(node, nodeDefinition) + /* Resolve Initial Values */ if (nodeDefinition.initialValues !== undefined) { if (nodeDefinition.initialValues.code !== undefined) { @@ -190,12 +213,17 @@ function newUiObjectsFromNodes () { node.code = nodeDefinition.initialValues.code } } + if (nodeDefinition.initialValues.config !== undefined) { + if (node.config === undefined) { + node.config = nodeDefinition.initialValues.config + } + } } /* For the cases where an node is not chained to its parent but to the one at the parent before it at its collection */ if (nodeDefinition.chainedToSameType === true) { if (parentNode !== undefined) { - let parentNodeDefinition = APP_SCHEMA_MAP.get(parentNode.type) + let parentNodeDefinition = getNodeDefinition(parentNode) if (parentNodeDefinition !== undefined) { if (parentNodeDefinition.properties !== undefined) { for (let i = 0; i < parentNodeDefinition.properties.length; i++) { @@ -263,12 +291,12 @@ function newUiObjectsFromNodes () { type: type } - let parentNodeDefinition = APP_SCHEMA_MAP.get(parentNode.type) + let parentNodeDefinition = getNodeDefinition(parentNode) if (parentNodeDefinition === undefined) { console.log('Cannot addUIOBject from parent of ' + type + ' because that type it is not defined at the APP_SCHEMA.') } /* Resolve Initial Values */ - let nodeDefinition = APP_SCHEMA_MAP.get(object.type) + let nodeDefinition = getNodeDefinition(object) if (nodeDefinition === undefined) { console.log('Cannot addUIOBject of ' + type + ' because that type it is not defined at the APP_SCHEMA.') @@ -279,6 +307,11 @@ function newUiObjectsFromNodes () { object.code = nodeDefinition.initialValues.code } } + if (nodeDefinition.initialValues !== undefined) { + if (nodeDefinition.initialValues.config !== undefined) { + object.config = nodeDefinition.initialValues.config + } + } if (nodeDefinition.isHierarchyHead === true) { parentNode.rootNodes.push(object) @@ -321,6 +354,9 @@ function newUiObjectsFromNodes () { if (nodeDefinition.initialValues.code !== undefined) { object.code = nodeDefinition.initialValues.code } + if (nodeDefinition.initialValues.config !== undefined) { + object.config = nodeDefinition.initialValues.config + } } } @@ -393,7 +429,7 @@ function newUiObjectsFromNodes () { } function addMissingChildren (node) { - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition(node) /* Connect to Parent */ if (nodeDefinition.properties !== undefined) { diff --git a/CanvasApp/DesignSpace/Workspace/Workspace.js b/CanvasApp/DesignSpace/Workspace/Workspace.js index 2680c323c..4cd40ff89 100644 --- a/CanvasApp/DesignSpace/Workspace/Workspace.js +++ b/CanvasApp/DesignSpace/Workspace/Workspace.js @@ -14,6 +14,7 @@ function newWorkspace () { replaceWorkspaceByLoadingOne: replaceWorkspaceByLoadingOne, save: saveWorkspace, getHierarchyHeads: getHierarchyHeads, + getHierarchyHeadsById: getHierarchyHeadsById, getNodeThatIsOnFocus: getNodeThatIsOnFocus, getNodeByShortcutKey: getNodeByShortcutKey, stopAllRunningTasks: stopAllRunningTasks, @@ -148,9 +149,9 @@ function newWorkspace () { let webSocketsPort /* At this point the node does not have the payload property yet, that is why we have to do this manually */ try { - let code = JSON.parse(networkNode.code) - host = code.host - webSocketsPort = code.webSocketsPort + let config = JSON.parse(networkNode.config) + host = config.host + webSocketsPort = config.webSocketsPort } catch (err) { console.log('[ERROR] networkNode ' + networkNode.name + ' has an invalid configuration. Cannot know the host name and webSocketsPort.') return @@ -345,7 +346,7 @@ function newWorkspace () { let nodes = [] for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { let rootNode = thisObject.workspaceNode.rootNodes[i] - let nodeDefinition = APP_SCHEMA_MAP.get(rootNode.type) + let nodeDefinition = getNodeDefinition(rootNode) if (nodeDefinition !== undefined) { if (nodeDefinition.isHierarchyHead === true) { nodes.push(rootNode) @@ -355,6 +356,16 @@ function newWorkspace () { return nodes } + function getHierarchyHeadsById (nodeId) { + let hiriatchyHeads = getHierarchyHeads() + for (let i = 0; i < hiriatchyHeads.length; i++) { + let hiriatchyHead = hiriatchyHeads[i] + if (hiriatchyHead.id === nodeId) { + return hiriatchyHead + } + } + } + function replaceWorkspaceByLoadingOne (name) { let blobService = newFileStorage() blobService.getFileFromHost('LoadWorkspace' + '/' + name, onFileReceived, true) @@ -561,7 +572,12 @@ function newWorkspace () { break case 'Run Session': { - functionLibrarySessionFunctions.runSession(payload.node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, callBackFunction) + functionLibrarySessionFunctions.runSession(payload.node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, false, callBackFunction) + } + break + case 'Resume Session': + { + functionLibrarySessionFunctions.runSession(payload.node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, true, callBackFunction) } break case 'Stop Session': @@ -596,7 +612,7 @@ function newWorkspace () { break case 'Open Documentation': { - let definition = APP_SCHEMA_MAP.get(payload.node.type) + let definition = getNodeDefinition(payload.node) if (definition !== undefined) { if (definition.docURL !== undefined) { let newTab = window.open(definition.docURL, '_blank') diff --git a/CanvasApp/FloatingSpace/ConfigEditor.js b/CanvasApp/FloatingSpace/ConfigEditor.js index b25331644..8cb2ffb83 100644 --- a/CanvasApp/FloatingSpace/ConfigEditor.js +++ b/CanvasApp/FloatingSpace/ConfigEditor.js @@ -52,10 +52,10 @@ function newConfigEditor () { thisObject.visible = false let textArea = document.getElementById('textArea') textArea.style.display = 'none' - thisObject.payload.node.code = textArea.value + thisObject.payload.node.config = textArea.value try { - let code = JSON.parse(thisObject.payload.node.code) + let config = JSON.parse(thisObject.payload.node.config) } catch (err) { thisObject.payload.uiObject.setErrorMessage(err.message, 10) } @@ -72,7 +72,7 @@ function newConfigEditor () { thisObject.payload.uiObject.setErrorMessage('', 0) let textArea = document.getElementById('textArea') - textArea.value = payload.node.code + textArea.value = payload.node.config textArea.style = 'resize: none;' + ' border: none;' + ' outline: none;' + @@ -94,7 +94,7 @@ function newConfigEditor () { if (thisObject.visible === true) { if (point.x === VERY_LARGE_NUMBER) { /* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that - anyone can react. In our case, the code editor has a text area that is not part of the canvas, so the event is + anyone can react. In our case, the config editor has a text area that is not part of the canvas, so the event is triggered. We compensate recognizing this coordinate and returning our container. */ return thisObject.container } diff --git a/CanvasApp/FloatingSpace/UiObject.js b/CanvasApp/FloatingSpace/UiObject.js index 1739df110..2e5bf0b2c 100644 --- a/CanvasApp/FloatingSpace/UiObject.js +++ b/CanvasApp/FloatingSpace/UiObject.js @@ -20,7 +20,6 @@ function newUiObject () { formulaEditor: undefined, uiObjectTitle: undefined, circularProgressBar: undefined, - isExecuting: undefined, isRunning: undefined, shortcutKey: undefined, isShowing: undefined, @@ -32,10 +31,15 @@ function newUiObject () { getReadyToReferenceAttach: getReadyToReferenceAttach, showAvailabilityToReferenceAttach: showAvailabilityToReferenceAttach, highlight: highlight, + runningAtBackend: runningAtBackend, setErrorMessage: setErrorMessage, + resetErrorMessage: resetErrorMessage, setValue: setValue, + resetValue: resetValue, setPercentage: setPercentage, + resetPercentage: resetPercentage, setStatus: setStatus, + resetStatus: resetStatus, physics: physics, invisiblePhysics: invisiblePhysics, drawBackground: drawBackground, @@ -67,6 +71,9 @@ function newUiObject () { let isHighlighted let highlightCounter = 0 + let isRunningAtBackend + let runningAtBackendCounter = 0 + let hasError let errorMessageCounter = 0 @@ -107,6 +114,7 @@ function newUiObject () { let currentStatus = '' let rightDragging = false + let eventSubscriptionIdOnError let eventSubscriptionIdOnRunning let eventSubscriptionIdOnStopped let lastHeartBeat @@ -178,6 +186,9 @@ function newUiObject () { if (eventSubscriptionIdOnStopped !== undefined) { eventsServerClient.stopListening(key, eventSubscriptionIdOnStopped, 'UiObject') } + if (eventSubscriptionIdOnError !== undefined) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnError, 'UiObject') + } } } @@ -313,6 +324,7 @@ function newUiObject () { iconPhysics() highlightPhisycs() + runningAtBackendPhisycs() errorMessagePhisycs() valuePhisycs() percentagePhisycs() @@ -363,7 +375,7 @@ function newUiObject () { } function childrenRunningPhysics () { - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition.properties === undefined) { return } let monitorChildrenRunning = false for (let i = 0; i < nodeDefinition.properties.length; i++) { @@ -407,7 +419,7 @@ function newUiObject () { let nearbyFloatingObjects = thisObject.payload.floatingObject.nearbyFloatingObjects let compatibleTypes - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.chainAttachesTo !== undefined) { compatibleTypes = nodeDefinition.chainAttachesTo.compatibleTypes @@ -429,10 +441,10 @@ function newUiObject () { let nearbyNode = floatingObject.payload.node if (compatibleTypes.indexOf('->' + nearbyNode.type + '->') >= 0) { /* Discard App Schema defined objects with busy coonection ports */ - nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition !== undefined) { let mustContinue = false - let parentNodeDefinition = APP_SCHEMA_MAP.get(nearbyNode.type) + let parentNodeDefinition = getNodeDefinition(nearbyNode) if (parentNodeDefinition !== undefined) { if (parentNodeDefinition.properties !== undefined) { for (let j = 0; j < parentNodeDefinition.properties.length; j++) { @@ -560,7 +572,7 @@ function newUiObject () { let nearbyFloatingObjects = thisObject.payload.floatingObject.nearbyFloatingObjects let compatibleTypes - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.referenceAttachesTo !== undefined) { compatibleTypes = nodeDefinition.referenceAttachesTo.compatibleTypes @@ -664,6 +676,14 @@ function newUiObject () { } } + function runningAtBackendPhisycs () { + runningAtBackendCounter-- + if (runningAtBackendCounter < 0) { + runningAtBackendCounter = 0 + isRunningAtBackend = false + } + } + function errorMessagePhisycs () { errorMessageCounter-- if (errorMessageCounter < 0) { @@ -696,9 +716,18 @@ function newUiObject () { } } - function highlight () { + function highlight (counter) { isHighlighted = true - highlightCounter = 30 + if (counter !== undefined) { + highlightCounter = counter + } else { + highlightCounter = 30 + } + } + + function runningAtBackend () { + isRunningAtBackend = true + runningAtBackendCounter = 5 } function setErrorMessage (message, duration) { @@ -710,6 +739,11 @@ function newUiObject () { } } + function resetErrorMessage () { + errorMessage = undefined + hasError = false + } + function setValue (value, counter) { if (value !== undefined) { currentValue = value @@ -722,6 +756,12 @@ function newUiObject () { } } + function resetValue () { + currentValue = undefined + hasValue = false + valueCounter = 0 + } + function setPercentage (percentage, counter) { if (percentage !== undefined) { currentPercentage = percentage @@ -734,6 +774,12 @@ function newUiObject () { } } + function resetPercentage () { + currentPercentage = undefined + hasPercentage = false + percentageCounter = 0 + } + function setStatus (status, counter) { if (status !== undefined) { currentStatus = status @@ -746,6 +792,12 @@ function newUiObject () { } } + function resetStatus () { + currentStatus = undefined + hasStatus = false + statusCounter = 0 + } + function heartBeat () { lastHeartBeat = new Date() thisObject.isRunning = true @@ -760,6 +812,10 @@ function newUiObject () { function run (pEventsServerClient, callBackFunction) { finalizeEventsServerClient() + resetErrorMessage() + resetPercentage() + resetValue() + resetStatus() eventsServerClient = pEventsServerClient /* We setup the circular progress bar. */ @@ -772,7 +828,12 @@ function newUiObject () { thisObject.circularProgressBar.fitFunction = thisObject.fitFunction thisObject.circularProgressBar.container = thisObject.container - /* We will wait to hear the Running event in order to confirm the execution was really started */ + setupRunningEventListener(callBackFunction) + setupErrorEventListener(callBackFunction) + } + + function setupRunningEventListener (callBackFunction) { + /* We will wait to hear the Running event in order to confirm the execution was really started */ let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id eventsServerClient.listenToEvent(key, 'Running', undefined, 'UiObject', onResponse, onRunning) @@ -813,6 +874,24 @@ function newUiObject () { stop(callBackFunction, event) } + function setupErrorEventListener (callBackFunction) { + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.listenToEvent(key, 'Error', undefined, key, onResponse, onError) + + function onResponse (message) { + eventSubscriptionIdOnError = message.eventSubscriptionId + } + + function onError (message) { + setErrorMessage(message.event.errorMessage, 10) + + let event = { + type: 'Secondary Action Already Executed' + } + completeStop(callBackFunction, event) + } + } + function stop (callBackFunction, event) { /* We will wait to the event that the execution was terminated in order to call back the menu item */ let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id @@ -823,26 +902,32 @@ function newUiObject () { } function onStopped () { - if (thisObject.payload === undefined) { return } - if (callBackFunction !== undefined) { - callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE, event) - } - - if (thisObject.circularProgressBar !== undefined) { - thisObject.circularProgressBar.finalize() - thisObject.circularProgressBar = undefined - } - thisObject.isRunning = false - hasValue = false - hasPercentage = false - hasStatus = false - lastHeartBeat = undefined + completeStop(callBackFunction, event) } } + function completeStop (callBackFunction, event) { + if (thisObject.payload === undefined) { return } + if (callBackFunction !== undefined) { + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE, event) + } + + if (thisObject.circularProgressBar !== undefined) { + thisObject.circularProgressBar.finalize() + thisObject.circularProgressBar = undefined + } + thisObject.isRunning = false + hasValue = false + hasPercentage = false + hasStatus = false + lastHeartBeat = undefined + + finalizeEventsServerClient() + } + function iconPhysics () { icon = canvas.designSpace.iconByUiObjectType.get(thisObject.payload.node.type) - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition.alternativeIcons !== undefined) { let nodeToUse = thisObject.payload.node if (nodeDefinition.alternativeIcons === 'Use Reference Parent') { @@ -859,15 +944,15 @@ function newUiObject () { } } } - nodeDefinition = APP_SCHEMA_MAP.get(nodeToUse.type) - let code = nodeToUse.code + nodeDefinition = getNodeDefinition(nodeToUse) + let config = nodeToUse.config try { - code = JSON.parse(code) + config = JSON.parse(config) let alternativeIcon let iconName for (let i = 0; i < nodeDefinition.alternativeIcons.length; i++) { alternativeIcon = nodeDefinition.alternativeIcons[i] - if (alternativeIcon.codeName === code.codeName) { + if (alternativeIcon.codeName === config.codeName) { iconName = alternativeIcon.iconName } } @@ -1261,7 +1346,7 @@ function newUiObject () { if (canvas.floatingSpace.inMapMode === true) { labelPoint.y = labelPoint.y - 20 - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.isHierarchyHead !== true) { return @@ -1291,7 +1376,7 @@ function newUiObject () { position = canvas.floatingSpace.transformPointToMap(position) } - let radius = thisObject.container.frame.radius * 3.5 + let radius = thisObject.container.frame.radius * 2.5 /* Label Text */ let labelPoint let fontSize = thisObject.payload.floatingObject.currentFontSize * 3 / 4 @@ -1351,7 +1436,7 @@ function newUiObject () { label = currentValue if (!isNaN(label)) { if (currentValue.toFixed !== undefined) { - label = dynamicDecimals(currentValue, 2) + label = dynamicDecimals(currentValue) } } @@ -1578,7 +1663,7 @@ function newUiObject () { browserCanvasContext.fill() - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.isHierarchyHead === true) { VISIBLE_RADIUS = thisObject.payload.floatingObject.currentHierarchyRing * 2.8 @@ -1637,7 +1722,7 @@ function newUiObject () { if (isHighlighted === true) { VISIBLE_RADIUS = thisObject.container.frame.radius * 1 - let OPACITY = highlightCounter / 30 + let OPACITY = highlightCounter / 10 browserCanvasContext.beginPath() browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) @@ -1756,11 +1841,11 @@ function newUiObject () { if (icon !== undefined) { if (icon.canDrawIcon === true) { let additionalImageSize = 0 - if (thisObject.isExecuting === true || isReadyToReferenceAttach === true || isReadyToChainAttach === true) { additionalImageSize = 20 } + if (isRunningAtBackend === true || isReadyToReferenceAttach === true || isReadyToChainAttach === true) { additionalImageSize = 20 } let totalImageSize = additionalImageSize + thisObject.payload.floatingObject.currentImageSize if (canvas.floatingSpace.inMapMode === true) { totalImageSize = canvas.floatingSpace.transformImagesizeToMap(totalImageSize) - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.isHierarchyHead !== true) { totalImageSize = totalImageSize / 4 @@ -1782,7 +1867,7 @@ function newUiObject () { if (executingIcon !== undefined) { if (executingIcon.canDrawIcon === true) { - if (thisObject.isExecuting === true) { + if (isRunningAtBackend === true) { const DISTANCE_FROM_CENTER = thisObject.container.frame.radius / 3 + 50 const EXECUTING_ICON_SIZE = 20 + thisObject.container.frame.radius / 6 diff --git a/CanvasApp/FloatingSpace/UiObjectConstructor.js b/CanvasApp/FloatingSpace/UiObjectConstructor.js index 0be9a5a24..b2445657a 100644 --- a/CanvasApp/FloatingSpace/UiObjectConstructor.js +++ b/CanvasApp/FloatingSpace/UiObjectConstructor.js @@ -74,7 +74,7 @@ function newUiObjectConstructor () { */ if (userAddingNew === true) { - let definition = APP_SCHEMA_MAP.get(payload.parentNode.type) + let definition = getNodeDefinition (payload.parentNode) if (definition.properties !== undefined) { for (let i = 0; i < definition.properties.length; i++) { let property = definition.properties[i] @@ -406,7 +406,7 @@ function newUiObjectConstructor () { function getMenuItemsInitialValues (uiObject, floatingObject, payload) { let menuItemsInitialValues = [] - let nodeDefinition = APP_SCHEMA_MAP.get(payload.node.type) + let nodeDefinition = getNodeDefinition (payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.editors !== undefined) { if (nodeDefinition.editors.config === true) { @@ -495,7 +495,7 @@ function newUiObjectConstructor () { } default: { - let nodeDefinition = APP_SCHEMA_MAP.get(payload.node.type) + let nodeDefinition = getNodeDefinition (payload.node) if (nodeDefinition !== undefined) { switch (nodeDefinition.level) { case 0: { diff --git a/CanvasApp/FloatingSpace/UiObjectTitle.js b/CanvasApp/FloatingSpace/UiObjectTitle.js index e96b23eac..89e39c5c0 100644 --- a/CanvasApp/FloatingSpace/UiObjectTitle.js +++ b/CanvasApp/FloatingSpace/UiObjectTitle.js @@ -51,7 +51,7 @@ function newUiObjectTitle () { thisObject.payload = payload thisObject.allwaysVisible = false // Default value - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition (thisObject.payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.isTitleAllwaysVisible === true) { thisObject.allwaysVisible = true @@ -100,7 +100,7 @@ function newUiObjectTitle () { if (thisObject.payload.title === undefined) { return } /* It is possible to override the default title by setting the APP SCHEMA property 'title' */ - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition (thisObject.payload.node) if (nodeDefinition.title !== undefined) { thisObject.payload.title = '' thisObject.payload.node.name = '' @@ -221,7 +221,7 @@ function newUiObjectTitle () { } function onMouseClick (event) { - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition (thisObject.payload.node) if (nodeDefinition.title !== undefined) { return } let checkPoint = { @@ -329,7 +329,7 @@ function newUiObjectTitle () { labelPoint.x = labelPoint.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO labelPoint.y = labelPoint.y - 35 - let nodeDefinition = APP_SCHEMA_MAP.get(thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition (thisObject.payload.node) if (nodeDefinition !== undefined) { if (nodeDefinition.isHierarchyHead !== true) { return diff --git a/CanvasApp/Panels/Layer.js b/CanvasApp/Panels/Layer.js index 1877a5ecc..cc2514911 100644 --- a/CanvasApp/Panels/Layer.js +++ b/CanvasApp/Panels/Layer.js @@ -157,6 +157,7 @@ function newLayer () { 'Session Reference->Session Independent Data->Session Based Data->Exchange Sessions->Session Based Data->Data Storage->Network Node->' + 'Data Storage->Network Node->' + 'Backtesting Session->Paper Trading Session->Fordward Testing Session->Live Trading Session->' + + 'Trading System Reference->Trading Engine Reference->Trading System->Trading Engine->' + 'Market->Market Base Asset->Asset->' + 'Market Quoted Asset->Asset->' + 'Exchange Markets->Crypto Exchange->' + @@ -173,12 +174,15 @@ function newLayer () { 'Polygon->Polygon Condition->Polygon Body->Style->Style Condition->Style->' + 'Polygon Border->Style->Style Condition->Style->' + 'Polygon Vertex->Point->' + - 'Plotter Panel->Javascript Code->Panel Data->Data Formula->' + 'Image->Image Condition->Image Position->Point->' + + 'Text->Text Condition->Text Position->Point->Text Formula->Text Style->' + + 'Plotter Panel->Javascript Code->Panel Data->Data Formula->' + + 'Nodes Highlights->Nodes Values->Nodes Errors->Nodes Status->Nodes Progress->Nodes Running->Nodes Announcements->Record Values->' thisObject.definition = functionLibraryProtocolNode.getProtocolNode(thisObject.payload.node, false, true, true, false, false, lightingPath) /* Here we validate that we have all the needed information */ - if (thisObject.definition.referenceParent === undefined) { return } - if (thisObject.definition.referenceParent.parentNode === undefined) { return } + if (thisObject.definition.referenceParent === undefined) { return } // Data Product + if (thisObject.definition.referenceParent.parentNode === undefined) { return } // Single Market Data if (thisObject.definition.referenceParent.parentNode.referenceParent === undefined) { return } if (thisObject.definition.referenceParent.parentNode.referenceParent.parentNode === undefined) { return } if (thisObject.definition.referenceParent.parentNode.referenceParent.parentNode.parentNode === undefined) { return } @@ -200,7 +204,7 @@ function newLayer () { /* Get ready to draw this layer */ thisObject.baseAsset = thisObject.definition.referenceParent.parentNode.referenceParent.baseAsset.referenceParent thisObject.quotedAsset = thisObject.definition.referenceParent.parentNode.referenceParent.quotedAsset.referenceParent - thisObject.market = thisObject.baseAsset.code.codeName + '/' + thisObject.quotedAsset.code.codeName + thisObject.market = thisObject.baseAsset.config.codeName + '/' + thisObject.quotedAsset.config.codeName thisObject.exchange = thisObject.definition.referenceParent.parentNode.referenceParent.parentNode.parentNode /* Some basic validations. */ @@ -223,11 +227,10 @@ function newLayer () { } thisObject.plotterModule = thisObject.definition.referenceParent.referenceParent.referenceParent - thisObject.exchangeIcon = getIcon(thisObject.exchange) - if (thisObject.plotterModule.code.icon !== undefined) { - thisObject.plotterTypeIcon = canvas.designSpace.iconCollection.get(thisObject.plotterModule.code.icon) + if (thisObject.plotterModule.config.icon !== undefined) { + thisObject.plotterTypeIcon = canvas.designSpace.iconCollection.get(thisObject.plotterModule.config.icon) } thisObject.baseAssetIcon = getIcon(thisObject.baseAsset) @@ -240,12 +243,12 @@ function newLayer () { } function getIcon (node) { - let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = getNodeDefinition(node) let iconName if (nodeDefinition.alternativeIcons !== undefined) { for (let i = 0; i < nodeDefinition.alternativeIcons.length; i++) { alternativeIcon = nodeDefinition.alternativeIcons[i] - if (alternativeIcon.codeName === node.code.codeName) { + if (alternativeIcon.codeName === node.config.codeName) { iconName = alternativeIcon.iconName } } diff --git a/CanvasApp/Panels/PlotterPanel.js b/CanvasApp/Panels/PlotterPanel.js index e50ebebde..3e03dbd86 100644 --- a/CanvasApp/Panels/PlotterPanel.js +++ b/CanvasApp/Panels/PlotterPanel.js @@ -100,9 +100,7 @@ function newPlotterPanel () { const PANEL_HEIGHT = UI_PANEL.HEIGHT.NORMAL if (currentRecord === undefined) { return } - if (currentRecord.data === undefined) { return } - - let record = currentRecord.data + let record = currentRecord /* First we execute code if provided. */ if (panelNode.javascriptCode !== undefined) { @@ -151,24 +149,24 @@ function newPlotterPanel () { } } - if (panelData.code.valueDecimals !== undefined) { + if (panelData.config.valueDecimals !== undefined) { if (value !== undefined) { if (isNaN(value) === false) { - value = dynamicDecimals(value, panelData.code.valueDecimals) + value = dynamicDecimals(value, panelData.config.valueDecimals) } } } - if (panelData.code.labelText !== undefined) { - labelText = panelData.code.labelText + if (panelData.config.labelText !== undefined) { + labelText = panelData.config.labelText } - if (panelData.code.labelPosition !== undefined) { - labelPosition = panelData.code.labelPosition + if (panelData.config.labelPosition !== undefined) { + labelPosition = panelData.config.labelPosition } - if (panelData.code.valuePosition !== undefined) { - valuePosition = panelData.code.valuePosition + if (panelData.config.valuePosition !== undefined) { + valuePosition = panelData.config.valuePosition } printLabel(labelText, X_AXIS, UI_PANEL.HEIGHT.NORMAL * labelPosition / 100 / heightFactor, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction) diff --git a/CanvasApp/Plotting/NodesAnnouncements.js b/CanvasApp/Plotting/NodesAnnouncements.js new file mode 100644 index 000000000..16116df03 --- /dev/null +++ b/CanvasApp/Plotting/NodesAnnouncements.js @@ -0,0 +1,46 @@ +function newNodesAnnouncements () { + const MODULE_NAME = 'Nodes Announcements' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize () { + hiriarchyMap = undefined + } + + function initialize (pRootNode) { + let rootNode = canvas.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = getHiriarchyMap(rootNode) + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.announcements + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue (nodeId, value) { + if (canvas.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setValue(value, 20) + } +} diff --git a/CanvasApp/Plotting/NodesErrors.js b/CanvasApp/Plotting/NodesErrors.js new file mode 100644 index 000000000..1a083597d --- /dev/null +++ b/CanvasApp/Plotting/NodesErrors.js @@ -0,0 +1,47 @@ +function newNodesErrors () { + const MODULE_NAME = 'Nodes Errors' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + let hiriarchyMap + return thisObject + + function finalize () { + hiriarchyMap = undefined + } + + function initialize (pRootNode) { + let rootNode = canvas.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = getHiriarchyMap(rootNode) + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.errors + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue (nodeId, value) { + if (canvas.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === '') { + node.payload.uiObject.resetErrorMessage() + } else { + node.payload.uiObject.setErrorMessage(value) + } + } +} diff --git a/CanvasApp/Plotting/NodesHighlights.js b/CanvasApp/Plotting/NodesHighlights.js new file mode 100644 index 000000000..443166ad6 --- /dev/null +++ b/CanvasApp/Plotting/NodesHighlights.js @@ -0,0 +1,42 @@ +function newNodesHighlights () { + const MODULE_NAME = 'Nodes Highlights' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize () { + hiriarchyMap = undefined + } + + function initialize (pRootNode) { + let rootNode = canvas.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = getHiriarchyMap(rootNode) + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.highlights + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let nodeId = array[i] + highlight(nodeId) + } + } + + function highlight (nodeId) { + if (canvas.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + node.payload.uiObject.highlight(10) + } +} diff --git a/CanvasApp/Plotting/NodesProgress.js b/CanvasApp/Plotting/NodesProgress.js new file mode 100644 index 000000000..cba36be9f --- /dev/null +++ b/CanvasApp/Plotting/NodesProgress.js @@ -0,0 +1,47 @@ +function newNodesProgress () { + const MODULE_NAME = 'Nodes Progress' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize () { + hiriarchyMap = undefined + } + + function initialize (pRootNode) { + let rootNode = canvas.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = getHiriarchyMap(rootNode) + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.progress + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue (nodeId, value) { + if (canvas.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setPercentage(value, 30) + } +} + diff --git a/CanvasApp/Plotting/NodesRunning.js b/CanvasApp/Plotting/NodesRunning.js new file mode 100644 index 000000000..130a7a8f8 --- /dev/null +++ b/CanvasApp/Plotting/NodesRunning.js @@ -0,0 +1,47 @@ +function newNodesRunning () { + const MODULE_NAME = 'Nodes Running' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize () { + hiriarchyMap = undefined + } + + function initialize (pRootNode) { + let rootNode = canvas.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = getHiriarchyMap(rootNode) + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.running + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue (nodeId, value) { + if (canvas.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.runningAtBackend() + } +} + diff --git a/CanvasApp/Plotting/NodesStatus.js b/CanvasApp/Plotting/NodesStatus.js new file mode 100644 index 000000000..699c70741 --- /dev/null +++ b/CanvasApp/Plotting/NodesStatus.js @@ -0,0 +1,47 @@ +function newNodesStatus () { + const MODULE_NAME = 'Nodes Status' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize () { + hiriarchyMap = undefined + } + + function initialize (pRootNode) { + let rootNode = canvas.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = getHiriarchyMap(rootNode) + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.status + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue (nodeId, value) { + if (canvas.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setStatus(value, 30) + } +} + diff --git a/CanvasApp/Plotting/NodesValues.js b/CanvasApp/Plotting/NodesValues.js new file mode 100644 index 000000000..53dfbfa60 --- /dev/null +++ b/CanvasApp/Plotting/NodesValues.js @@ -0,0 +1,47 @@ +function newNodesValues () { + const MODULE_NAME = 'Nodes Values' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize () { + hiriarchyMap = undefined + } + + function initialize (pRootNode) { + let rootNode = canvas.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = getHiriarchyMap(rootNode) + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.values + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue (nodeId, value) { + if (canvas.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setValue(value, 1) + } +} + diff --git a/CanvasApp/Plotter.js b/CanvasApp/Plotting/Plotter.js similarity index 86% rename from CanvasApp/Plotter.js rename to CanvasApp/Plotting/Plotter.js index ff1bf1368..bdb2d3d05 100644 --- a/CanvasApp/Plotter.js +++ b/CanvasApp/Plotting/Plotter.js @@ -245,13 +245,14 @@ function newPlotter () { function draw () { try { thisObject.container.frame.draw() - plotChart() + plot() } catch (err) { if (ERROR_LOG === true) { logger.write('[ERROR] draw -> err = ' + err.stack) } } } function recalculate () { + if (canvas.chartingSpace.visible !== true) { return } try { if (timeFrame >= _1_HOUR_IN_MILISECONDS) { recalculateUsingMarketFiles() @@ -277,8 +278,8 @@ function newPlotter () { let record = {} for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { - record[property.code.codeName] = dataFile[i][j] + if (property.config.isCalculated !== true) { + record[property.config.codeName] = dataFile[i][j] } } @@ -310,7 +311,7 @@ function newPlotter () { timeFrame: timeFrame, ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS } - let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. + let variable = {} // This is the structure where the user will define its own variables that will be shared across different config blocks and formulas. let results = [] /* This is Initialization Code */ @@ -345,13 +346,13 @@ function newPlotter () { /* For each calculated property we apply its formula */ for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated === true) { + if (property.config.isCalculated === true) { if (property.formula !== undefined) { if (property.formula.code !== undefined) { try { let newValue = eval(property.formula.code) let currentRecord = product - currentRecord[property.code.codeName] = newValue + currentRecord[property.config.codeName] = newValue } catch (err) { logger.write('[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Error = ' + err.stack.stack) logger.write('[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. product = ' + JSON.stringify(product)) @@ -489,13 +490,10 @@ function newPlotter () { } } - function plotChart () { + function plot () { try { /* Clean the pannel at places where there is no record. */ - let currentRecord = { - data: undefined - } - thisObject.container.eventHandler.raiseEvent('Current Record Changed', currentRecord) + thisObject.container.eventHandler.raiseEvent('Current Record Changed', undefined) for (let i = 0; i < records.length; i++) { let record = records[i] @@ -505,7 +503,7 @@ function newPlotter () { To enable that we need to link all records to the previous one in this way. */ if (i == 0) { - record.previous = record + record.previous = {} // this way it wont be undefined } else { record.previous = records[i - 1] } @@ -539,11 +537,7 @@ function newPlotter () { let atMousePosition = false if (userPositionDate >= record.begin && userPositionDate <= record.end) { atMousePosition = true - - let currentRecord = { - data: record - } - thisObject.container.eventHandler.raiseEvent('Current Record Changed', currentRecord) + thisObject.container.eventHandler.raiseEvent('Current Record Changed', record) } /* If there is code we execute it now. */ @@ -596,6 +590,7 @@ function newPlotter () { record.dataPoints = dataPoints } + /* Here we are going to plot all the polygons. */ for (let j = 0; j < productDefinition.referenceParent.shapes.polygons.length; j++) { let polygon = productDefinition.referenceParent.shapes.polygons[j] /* We will check if we need to plot this Polygon or not. */ @@ -622,9 +617,9 @@ function newPlotter () { } else { /* Get the default style if exists */ if (polygon.polygonBody.style !== undefined) { - if (polygon.polygonBody.style.code !== undefined) { - if (polygon.polygonBody.style.code.default !== undefined) { - let bodyStyle = polygon.polygonBody.style.code.default + if (polygon.polygonBody.style.config !== undefined) { + if (polygon.polygonBody.style.config.default !== undefined) { + let bodyStyle = polygon.polygonBody.style.config.default if (bodyStyle.opacity !== undefined) { fillStyle.opacity = bodyStyle.opacity } if (bodyStyle.paletteColor !== undefined) { fillStyle.paletteColor = eval(bodyStyle.paletteColor) } } @@ -637,7 +632,7 @@ function newPlotter () { let value = eval(condition.code) if (value === true) { if (condition.style !== undefined) { - let bodyStyle = condition.style.code + let bodyStyle = condition.style.config if (bodyStyle.opacity !== undefined) { fillStyle.opacity = bodyStyle.opacity } if (bodyStyle.paletteColor !== undefined) { fillStyle.paletteColor = eval(bodyStyle.paletteColor) } } @@ -646,8 +641,8 @@ function newPlotter () { } /* Get the atMousePosition style if exists */ if (polygon.polygonBody.style !== undefined) { - if (polygon.polygonBody.style.code.atMousePosition !== undefined) { - let atMousePositionStyleDefinition = polygon.polygonBody.style.code.atMousePosition + if (polygon.polygonBody.style.config.atMousePosition !== undefined) { + let atMousePositionStyleDefinition = polygon.polygonBody.style.config.atMousePosition let atMousePositionStyle = {} if (atMousePositionStyleDefinition.opacity !== undefined) { atMousePositionStyle.opacity = atMousePositionStyleDefinition.opacity } if (atMousePositionStyleDefinition.paletteColor !== undefined) { atMousePositionStyle.paletteColor = eval(atMousePositionStyleDefinition.paletteColor) } @@ -677,9 +672,9 @@ function newPlotter () { } else { /* Get the default style if exists */ if (polygon.polygonBorder.style !== undefined) { - if (polygon.polygonBorder.style.code !== undefined) { - if (polygon.polygonBorder.style.code.default !== undefined) { - let borderStyle = polygon.polygonBorder.style.code.default + if (polygon.polygonBorder.style.config !== undefined) { + if (polygon.polygonBorder.style.config.default !== undefined) { + let borderStyle = polygon.polygonBorder.style.config.default if (borderStyle.opacity !== undefined) { strokeStyle.opacity = borderStyle.opacity } if (borderStyle.paletteColor !== undefined) { strokeStyle.paletteColor = eval(borderStyle.paletteColor) } if (borderStyle.lineWidth !== undefined) { strokeStyle.lineWidth = borderStyle.lineWidth } @@ -694,7 +689,7 @@ function newPlotter () { let value = eval(condition.code) if (value === true) { if (condition.style !== undefined) { - let borderStyle = condition.style.code + let borderStyle = condition.style.config if (borderStyle.opacity !== undefined) { strokeStyle.opacity = borderStyle.opacity } if (borderStyle.paletteColor !== undefined) { strokeStyle.paletteColor = eval(borderStyle.paletteColor) } if (borderStyle.lineWidth !== undefined) { strokeStyle.lineWidth = borderStyle.lineWidth } @@ -705,8 +700,8 @@ function newPlotter () { } /* Get the atMousePosition style if exists */ if (polygon.polygonBorder.style !== undefined) { - if (polygon.polygonBorder.style.code.atMousePosition !== undefined) { - let atMousePositionStyleDefinition = polygon.polygonBorder.style.code.atMousePosition + if (polygon.polygonBorder.style.config.atMousePosition !== undefined) { + let atMousePositionStyleDefinition = polygon.polygonBorder.style.config.atMousePosition let atMousePositionStyle = {} if (atMousePositionStyleDefinition.opacity !== undefined) { atMousePositionStyle.opacity = atMousePositionStyleDefinition.opacity } if (atMousePositionStyleDefinition.paletteColor !== undefined) { atMousePositionStyle.paletteColor = eval(atMousePositionStyleDefinition.paletteColor) } @@ -782,6 +777,79 @@ function newPlotter () { browserCanvasContext.stroke() } } + + /* Here we are going to plot images. */ + for (let j = 0; j < productDefinition.referenceParent.shapes.images.length; j++) { + let image = productDefinition.referenceParent.shapes.images[j] + if (image.imagePosition === undefined) { continue } + if (image.imagePosition.referenceParent === undefined) { continue } + if (image.imageCondition !== undefined) { + let mustPlot = eval(image.imageCondition.code) + if (mustPlot !== true) { continue } + } + let imageName = '' + let imageSize = 0 + let offsetX = 0 + let offsetY = 0 + let imagePosition = {x: 0, y: 0} + if (image.config.codeName !== undefined) { imageName = image.config.codeName } + if (image.config.size !== undefined) { imageSize = image.config.size } + if (image.imagePosition.config.offsetX !== undefined) { offsetX = image.imagePosition.config.offsetX } + if (image.imagePosition.config.offsetY !== undefined) { offsetY = image.imagePosition.config.offsetY } + let dataPointObject = dataPoints.get(image.imagePosition.referenceParent.id) + if (dataPointObject === undefined) { continue } + let dataPoint = { + x: dataPointObject.x, + y: dataPointObject.y + } + dataPoint = canvas.chartingSpace.viewport.fitIntoVisibleArea(dataPoint) + dataPoint = thisObject.fitFunction(dataPoint) + imagePosition.x = dataPoint.x + imagePosition.y = dataPoint.y + let imageToDraw = canvas.designSpace.iconCollection.get(imageName) + if (imageToDraw.canDrawIcon === true) { + browserCanvasContext.drawImage(imageToDraw, imagePosition.x - imageSize / 2 + offsetX, imagePosition.y - imageSize / 2 - offsetY, imageSize, imageSize) + } + } + + /* Here we are going to plot texts. */ + for (let j = 0; j < productDefinition.referenceParent.shapes.texts.length; j++) { + let text = productDefinition.referenceParent.shapes.texts[j] + if (text.textPosition === undefined) { continue } + if (text.textPosition.referenceParent === undefined) { continue } + if (text.textCondition !== undefined) { + let mustPlot = eval(text.textCondition.code) + if (mustPlot !== true) { continue } + } + if (text.textFormula === undefined) { continue } + if (text.textStyle === undefined) { continue } + let value = eval(text.textFormula.code) + let fontSize = 0 + let opacity = 0 + let paletteColor = UI_COLOR.GREY + let offsetX = 0 + let offsetY = 0 + let textPosition = {x: 0, y: 0} + if (text.textStyle.config.fontSize !== undefined) { fontSize = text.textStyle.config.fontSize } + if (text.textStyle.config.opacity !== undefined) { opacity = text.textStyle.config.opacity } + if (text.textStyle.config.paletteColor !== undefined) { paletteColor = eval(text.textStyle.config.paletteColor) } + if (text.textPosition.config.offsetX !== undefined) { offsetX = text.textPosition.config.offsetX } + if (text.textPosition.config.offsetY !== undefined) { offsetY = text.textPosition.config.offsetY } + let dataPointObject = dataPoints.get(text.textPosition.referenceParent.id) + if (dataPointObject === undefined) { continue } + let dataPoint = { + x: dataPointObject.x, + y: dataPointObject.y + } + dataPoint = canvas.chartingSpace.viewport.fitIntoVisibleArea(dataPoint) + dataPoint = thisObject.fitFunction(dataPoint) + textPosition.x = dataPoint.x + textPosition.y = dataPoint.y + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + paletteColor + ', ' + opacity + ')' + browserCanvasContext.fillText(value, textPosition.x + offsetX, textPosition.y - offsetY) + } } if (coordinateSystem.autoMinYScale === true || coordinateSystem.autoMinYScale === true) { @@ -792,7 +860,7 @@ function newPlotter () { logged = false } catch (err) { - if (ERROR_LOG === true) { logger.write('[ERROR] plotChart -> err = ' + err.stack) } + if (ERROR_LOG === true) { logger.write('[ERROR] plot -> err = ' + err.stack) } } } @@ -806,11 +874,7 @@ function newPlotter () { } function onDragFinished () { - try { - recalculate() - } catch (err) { - if (ERROR_LOG === true) { logger.write('[ERROR] onDragFinished -> err = ' + err.stack) } - } + recalculate() } function onDisplace (event) { diff --git a/CanvasApp/Plotting/RecordValues.js b/CanvasApp/Plotting/RecordValues.js new file mode 100644 index 000000000..2e66ad2da --- /dev/null +++ b/CanvasApp/Plotting/RecordValues.js @@ -0,0 +1,64 @@ +function newRecordValues () { + const MODULE_NAME = 'Record Values' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let tradingSystem + let tradingEngine + let productDefinition + let productTargetNode + let propertyTargetNodeMap = new Map() + return thisObject + + function finalize () { + tradingSystem = undefined + tradingEngine = undefined + productDefinition = undefined + productTargetNode = undefined + propertyTargetNodeMap = undefined + } + + function initialize (pTradingSystem, pTradingEngine, pProductDefinition) { + tradingSystem = canvas.designSpace.workspace.getHierarchyHeadsById(pTradingSystem.id) + tradingEngine = canvas.designSpace.workspace.getHierarchyHeadsById(pTradingEngine.id) + productDefinition = pProductDefinition + productTargetNode = eval(productDefinition.config.nodePath) + + for (let i = 0; i < productDefinition.record.properties.length; i++) { + let property = productDefinition.record.properties[i] + if (property.config.nodePath !== undefined) { + let propertyRoot = eval(property.config.nodePath) + propertyTargetNodeMap.set(property.id, propertyRoot) + } else { + propertyTargetNodeMap.set(property.id, productTargetNode) + } + } + } + + function onRecordChange (currentRecord) { + if (currentRecord === undefined) { return } + for (let i = 0; i < productDefinition.record.properties.length; i++) { + let property = productDefinition.record.properties[i] + let value = currentRecord[property.config.codeName] + let targetNode = propertyTargetNodeMap.get(property.id) + applyValue(targetNode[property.config.codeName], value) + } + } + + function applyValue (node, value) { + if (canvas.chartingSpace.visible !== true) { return } + if (node === undefined) { return } + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setValue(value, 1) + } +} diff --git a/CanvasApp/PostLoader.js b/CanvasApp/PostLoader.js index d47eb8add..82c268bec 100644 --- a/CanvasApp/PostLoader.js +++ b/CanvasApp/PostLoader.js @@ -29,10 +29,8 @@ function newDashboard () { for (let i = 0; i < APP_SCHEMA_ARRAY.length; i++) { let nodeDefinition = APP_SCHEMA_ARRAY[i] let key = nodeDefinition.type - APP_SCHEMA_MAP.set(key, nodeDefinition) } - startCanvas() } diff --git a/CanvasApp/ProductStorage.js b/CanvasApp/ProductStorage.js index 68c76ef1b..57c8e6b8a 100644 --- a/CanvasApp/ProductStorage.js +++ b/CanvasApp/ProductStorage.js @@ -85,7 +85,7 @@ function newProductStorage (name) { for (let i = 0; i < product.datasets.length; i++) { let dataset = product.datasets[i] - switch (dataset.code.type) { + switch (dataset.config.type) { case 'Market Files': { dataSetsToLoad++ @@ -122,7 +122,7 @@ function newProductStorage (name) { } break default: - if (ERROR_LOG === true) { logger.write('[WARN] initialize -> dataset with no type defined or type not supported -> dataset.code.type = ' + dataset.code.type) } + if (ERROR_LOG === true) { logger.write('[WARN] initialize -> dataset with no type defined or type not supported -> dataset.config.type = ' + dataset.config.type) } } function onMarketFileReady (err, pCaller) { diff --git a/CanvasApp/Scales/RateScale.js b/CanvasApp/Scales/RateScale.js index 6964143fd..93ca0cf9d 100644 --- a/CanvasApp/Scales/RateScale.js +++ b/CanvasApp/Scales/RateScale.js @@ -255,12 +255,12 @@ function newRateScale () { function saveObjectState () { try { - let code = JSON.parse(thisObject.payload.node.code) - code.minValue = coordinateSystem.min.y - code.maxValue = coordinateSystem.max.y - code.autoMinScale = coordinateSystem.autoMinYScale - code.autoMaxScale = coordinateSystem.autoMaxYScale - thisObject.payload.node.code = JSON.stringify(code, null, 4) + let config = JSON.parse(thisObject.payload.node.config) + config.minValue = coordinateSystem.min.y + config.maxValue = coordinateSystem.max.y + config.autoMinScale = coordinateSystem.autoMinYScale + config.autoMaxScale = coordinateSystem.autoMaxYScale + thisObject.payload.node.config = JSON.stringify(config, null, 4) } catch (err) { // we ignore errors here since most likely they will be parsing errors. } @@ -268,29 +268,29 @@ function newRateScale () { function readObjectState () { try { - let code = JSON.parse(thisObject.payload.node.code) + let config = JSON.parse(thisObject.payload.node.config) if ( - (isNaN(code.minValue) || code.minValue === null || code.minValue === undefined) || - (isNaN(code.maxValue) || code.maxValue === null || code.maxValue === undefined) + (isNaN(config.minValue) || config.minValue === null || config.minValue === undefined) || + (isNaN(config.maxValue) || config.maxValue === null || config.maxValue === undefined) ) { // not using this value } else { if ( - thisObject.minValue !== code.minValue || - thisObject.maxValue !== code.maxValue || - coordinateSystem.autoMinYScale !== code.autoMinScale || - coordinateSystem.autoMaxYScale !== code.autoMaxScale + thisObject.minValue !== config.minValue || + thisObject.maxValue !== config.maxValue || + coordinateSystem.autoMinYScale !== config.autoMinScale || + coordinateSystem.autoMaxYScale !== config.autoMaxScale ) { - thisObject.minValue = code.minValue - thisObject.maxValue = code.maxValue + thisObject.minValue = config.minValue + thisObject.maxValue = config.maxValue coordinateSystem.min.y = thisObject.minValue coordinateSystem.max.y = thisObject.maxValue - if (code.autoMinScale !== undefined && (code.autoMinScale === true || code.autoMinScale === false) && code.autoMaxScale !== undefined && (code.autoMaxScale === true || code.autoMaxScale === false)) { - coordinateSystem.autoMinYScale = code.autoMinScale - coordinateSystem.autoMaxYScale = code.autoMaxScale - autoScaleButton.setStatus(code.autoMinScale, code.autoMaxScale) + if (config.autoMinScale !== undefined && (config.autoMinScale === true || config.autoMinScale === false) && config.autoMaxScale !== undefined && (config.autoMaxScale === true || config.autoMaxScale === false)) { + coordinateSystem.autoMinYScale = config.autoMinScale + coordinateSystem.autoMaxYScale = config.autoMaxScale + autoScaleButton.setStatus(config.autoMinScale, config.autoMaxScale) } coordinateSystem.recalculateScale() diff --git a/CanvasApp/Scales/TimeFrameScale.js b/CanvasApp/Scales/TimeFrameScale.js index 89fdebc8e..3d0d41cd0 100644 --- a/CanvasApp/Scales/TimeFrameScale.js +++ b/CanvasApp/Scales/TimeFrameScale.js @@ -313,9 +313,9 @@ function newTimeFrameScale () { function saveObjectState () { try { - let code = JSON.parse(thisObject.payload.node.code) - code.value = timeFrameLabel - thisObject.payload.node.code = JSON.stringify(code) + let config = JSON.parse(thisObject.payload.node.config) + config.value = timeFrameLabel + thisObject.payload.node.config = JSON.stringify(config) } catch (err) { // we ignore errors here since most likely they will be parsing errors. } @@ -323,14 +323,14 @@ function newTimeFrameScale () { function readObjectState () { try { - let code = JSON.parse(thisObject.payload.node.code) - if (code.value !== timeFrameLabel) { + let config = JSON.parse(thisObject.payload.node.config) + if (config.value !== timeFrameLabel) { let found = false for (let i = 0; i < timeFramesMasterArray.length; i++) { let timeFrameArray = timeFramesMasterArray[i] for (let j = 0; j < timeFrameArray.length; j++) { let record = timeFrameArray[j] - if (code.value === record[1]) { + if (config.value === record[1]) { filePeriodIndex = i timeFrameIndex = j found = true diff --git a/CanvasApp/Scales/TimeScale.js b/CanvasApp/Scales/TimeScale.js index 00d52c530..90ee96fb0 100644 --- a/CanvasApp/Scales/TimeScale.js +++ b/CanvasApp/Scales/TimeScale.js @@ -172,12 +172,12 @@ function newTimeScale () { function saveObjectState () { try { - let code = JSON.parse(thisObject.payload.node.code) - code.fromDate = (new Date(coordinateSystem.min.x)).toISOString() - code.toDate = (new Date(coordinateSystem.max.x)).toISOString() - code.autoMinScale = coordinateSystem.autoMinXScale - code.autoMaxScale = coordinateSystem.autoMaxXScale - thisObject.payload.node.code = JSON.stringify(code, null, 4) + let config = JSON.parse(thisObject.payload.node.config) + config.fromDate = (new Date(coordinateSystem.min.x)).toISOString() + config.toDate = (new Date(coordinateSystem.max.x)).toISOString() + config.autoMinScale = coordinateSystem.autoMinXScale + config.autoMaxScale = coordinateSystem.autoMaxXScale + thisObject.payload.node.config = JSON.stringify(config, null, 4) } catch (err) { // we ignore errors here since most likely they will be parsing errors. } @@ -185,24 +185,24 @@ function newTimeScale () { function readObjectState () { try { - let code = JSON.parse(thisObject.payload.node.code) + let config = JSON.parse(thisObject.payload.node.config) if ( - (isNaN(Date.parse(code.fromDate)) || code.fromDate === null || code.fromDate === undefined) || - (isNaN(Date.parse(code.toDate)) || code.toDate === null || code.toDate === undefined) + (isNaN(Date.parse(config.fromDate)) || config.fromDate === null || config.fromDate === undefined) || + (isNaN(Date.parse(config.toDate)) || config.toDate === null || config.toDate === undefined) ) { // not using this value } else { - if (thisObject.fromDate !== Date.parse(code.fromDate) || thisObject.toDate !== Date.parse(code.toDate)) { - thisObject.fromDate = Date.parse(code.fromDate) - thisObject.toDate = Date.parse(code.toDate) + if (thisObject.fromDate !== Date.parse(config.fromDate) || thisObject.toDate !== Date.parse(config.toDate)) { + thisObject.fromDate = Date.parse(config.fromDate) + thisObject.toDate = Date.parse(config.toDate) coordinateSystem.min.x = thisObject.fromDate coordinateSystem.max.x = thisObject.toDate - if (code.autoMinScale !== undefined && (code.autoMinScale === true || code.autoMinScale === false) && code.autoMaxScale !== undefined && (code.autoMaxScale === true || code.autoMaxScale === false)) { - coordinateSystem.autoMinXScale = code.autoMinScale - coordinateSystem.autoMaxXScale = code.autoMaxScale - autoScaleButton.setStatus(code.autoMinScale, code.autoMaxScale) + if (config.autoMinScale !== undefined && (config.autoMinScale === true || config.autoMinScale === false) && config.autoMaxScale !== undefined && (config.autoMaxScale === true || config.autoMaxScale === false)) { + coordinateSystem.autoMinXScale = config.autoMinScale + coordinateSystem.autoMaxXScale = config.autoMaxScale + autoScaleButton.setStatus(config.autoMinScale, config.autoMaxScale) } coordinateSystem.recalculateScale() } diff --git a/CanvasApp/Spaces/DesignSpace.js b/CanvasApp/Spaces/DesignSpace.js index 305e1d532..4940727b6 100644 --- a/CanvasApp/Spaces/DesignSpace.js +++ b/CanvasApp/Spaces/DesignSpace.js @@ -285,7 +285,13 @@ function newDesignSpace () { 'arrangement-vertical-right', 'arrangement-vertical-left', 'arrangement-concave', - 'move-to-phase-event' + 'move-to-phase-event', + 'open-execution', + 'move-order', + 'market-order', + 'limit-order', + 'execution-algorithm', + 'cancel-order' ] for (let i = 0; i < iconsNames.length; i++) { diff --git a/CanvasApp/Spaces/PanelsSpace.js b/CanvasApp/Spaces/PanelsSpace.js index e752631d1..a634fa26e 100644 --- a/CanvasApp/Spaces/PanelsSpace.js +++ b/CanvasApp/Spaces/PanelsSpace.js @@ -50,14 +50,14 @@ function newPanelsSpace () { } case 'Plotter Panel': { - if (pParameters.panelNode.code.isLegacy !== true) { + if (pParameters.panelNode.config.isLegacy !== true) { panel = newPlotterPanel() panel.fitFunction = canvas.chartingSpace.fitFunction panel.container.isVisibleFunction = canvas.chartingSpace.isThisPointVisible panel.session = pSession panel.initialize(pParameters.panelNode) } else { - panel = getNewPlotterPanel(pParameters.dataMine, pParameters.plotterCodeName, pParameters.moduleCodeName, pParameters.panelNode.code.codeName) + panel = getNewPlotterPanel(pParameters.dataMine, pParameters.plotterCodeName, pParameters.moduleCodeName, pParameters.panelNode.config.codeName) panel.fitFunction = canvas.chartingSpace.fitFunction panel.container.isVisibleFunction = canvas.chartingSpace.isThisPointVisible panel.session = pSession diff --git a/CanvasApp/SplashScreen.js b/CanvasApp/SplashScreen.js index 34b7c9589..6e7dfa436 100644 --- a/CanvasApp/SplashScreen.js +++ b/CanvasApp/SplashScreen.js @@ -125,7 +125,7 @@ function newSplashScreen () { fontSize = 35 - title = 'Beta 5' + title = 'Beta 6' titlePoint = { x: thisObject.container.frame.width / 2 - title.length / 2 * fontSize * FONT_ASPECT_RATIO - 20, diff --git a/CanvasApp/Utilities.js b/CanvasApp/Utilities.js index 2af862981..4a434bba8 100644 --- a/CanvasApp/Utilities.js +++ b/CanvasApp/Utilities.js @@ -1,25 +1,6 @@ -function drawContainerBackground (container, color, opacity, fitFunction) { - let fromPoint = { - x: 0, - y: 0 - } - - let toPoint = { - x: container.frame.width, - y: container.frame.height - } - - fromPoint = transformThisPoint(fromPoint, container) - toPoint = transformThisPoint(toPoint, container) - - fromPoint = fitFunction(fromPoint) - toPoint = fitFunction(toPoint) - - browserCanvasContext.beginPath() - browserCanvasContext.rect(fromPoint.x, fromPoint.y, toPoint.x - fromPoint.x, toPoint.y - fromPoint.y) - browserCanvasContext.fillStyle = 'rgba(' + color + ', ' + opacity + ')' - browserCanvasContext.closePath() - browserCanvasContext.fill() +function getNodeDefinition (node) { + let key = node.type + return APP_SCHEMA_MAP.get(key) } function dynamicDecimals (value, minDecimals) { @@ -38,50 +19,6 @@ function dynamicDecimals (value, minDecimals) { return returnValue } -function savePropertyAtNodeConfig (payload, propertyName, value) { - try { - let code = JSON.parse(payload.node.code) - code[propertyName] = value - payload.node.code = JSON.stringify(code, null, 4) - } catch (err) { - // we ignore errors here since most likely they will be parsing errors. - } -} - -function loadPropertyFromNodeConfig (payload, propertyName) { - try { - let code = JSON.parse(payload.node.code) - return code[propertyName] - } catch (err) { - // we ignore errors here since most likely they will be parsing errors. - } -} - -function saveFrame (payload, frame) { - payload.frame = { - position: { - x: frame.position.x, - y: frame.position.y - }, - width: frame.width, - height: frame.height, - radius: frame.radius - } -} - -function loadFrame (payload, frame) { - if (payload.node.savedPayload !== undefined) { - if (payload.node.savedPayload.frame !== undefined) { - frame.position.x = payload.node.savedPayload.frame.position.x - frame.position.y = payload.node.savedPayload.frame.position.y - frame.width = payload.node.savedPayload.frame.width - frame.height = payload.node.savedPayload.frame.height - frame.radius = payload.node.savedPayload.frame.radius - payload.node.savedPayload.frame = undefined - } - } -} - function convertTimeFrameToName (pTimeFrame) { for (let i = 0; i < dailyFilePeriods.length; i++) { let period = dailyFilePeriods[i] @@ -98,84 +35,6 @@ function convertTimeFrameToName (pTimeFrame) { } } -function downloadText (filename, text) { - let element = document.createElement('a') - element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)) - element.setAttribute('download', filename) - element.style.display = 'none' - document.body.appendChild(element) - element.click() - document.body.removeChild(element) -} - -function downloadCanvas (filename, canvasToUse) { - let data = canvasToUse.toDataURL('image/png', 1) - /* Change MIME type to trick the browser to downlaod the file instead of displaying it */ - data = data.replace(/^data:image\/[^;]*/, 'data:application/octet-stream') - - /* In addition to 's "download" attribute, you can define HTTP-style headers */ - data = data.replace(/^data:application\/octet-stream/, 'data:application/octet-stream;headers=Content-Disposition%3A%20attachment%3B%20filename=' + filename + '.png') - - let element = document.createElement('a') - element.setAttribute('href', data) - element.setAttribute('download', filename + '.PNG') - element.style.display = 'none' - document.body.appendChild(element) - element.click() - document.body.removeChild(element) -}; - -function downloadPanorama (filename) { - let INITIAL_WIDTH = 800 - let FINAL_WIDTH = browserCanvas.width - INITIAL_WIDTH - - if (ARE_WE_RECORDING_A_MARKET_PANORAMA === false) { - /* We start recording the panorame. */ - ARE_WE_RECORDING_A_MARKET_PANORAMA = true - marketPanoramaCanvas = document.createElement('canvas') - marketPanoramaCanvas.width = INITIAL_WIDTH * 50 - marketPanoramaCanvas.height = browserCanvas.height - marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, 0, 0, INITIAL_WIDTH, browserCanvas.height, 0, 0, INITIAL_WIDTH, browserCanvas.height) - CURRENT_PANORAMA_POSITION = INITIAL_WIDTH - } else { - /* We stop recording and download the resulting image. */ - ARE_WE_RECORDING_A_MARKET_PANORAMA = false - marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, browserCanvas.width - FINAL_WIDTH, 0, FINAL_WIDTH, browserCanvas.height, CURRENT_PANORAMA_POSITION, 0, FINAL_WIDTH, browserCanvas.height) - let finalResultCanvas = document.createElement('canvas') - finalResultCanvas.width = CURRENT_PANORAMA_POSITION + FINAL_WIDTH - finalResultCanvas.height = browserCanvas.height - finalResultCanvas.getContext('2d').drawImage(marketPanoramaCanvas, 0, 0, finalResultCanvas.width, browserCanvas.height, 0, 0, finalResultCanvas.width, browserCanvas.height) - downloadCanvas(filename, finalResultCanvas) - marketPanoramaCanvas = undefined - } -} - -function addToMarketPanorama () { - let SECTION_WIDTH = 80 * 2 - let INITIAL_POSITION = 800 - SECTION_WIDTH - marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, INITIAL_POSITION, 0, SECTION_WIDTH, browserCanvas.height, CURRENT_PANORAMA_POSITION, 0, SECTION_WIDTH, browserCanvas.height) - CURRENT_PANORAMA_POSITION = CURRENT_PANORAMA_POSITION + SECTION_WIDTH -} - -function transformThisPoint (point, container) { - /* We make the point relative to the current frame */ - - point = container.frame.frameThisPoint(point) - - /* We pass this point through the canvas.chartingSpace.viewport lends, meaning we apply the canvas.chartingSpace.viewport zoom and displacement. */ - - point = canvas.chartingSpace.viewport.transformThisPoint(point) - - return point -} - -function unTransformThisPoint (point, container) { - point = canvas.chartingSpace.viewport.unTransformThisPoint(point) - point = container.frame.unframeThisPoint(point) - - return point -} - function nextPorwerOf10 (number) { for (let i = -10; i <= 10; i++) { if (number < Math.pow(10, i)) { @@ -189,59 +48,6 @@ function pad (str, max) { return str.length < max ? pad('0' + str, max) : str } -function getDateFromPointAtBrowserCanvas (point, container, coordinateSystem) { - point = unTransformThisPoint(point, container) - point = coordinateSystem.unInverseTransform(point, container.frame.height) - - let date = new Date(point.x) - - return date -} - -function getDateFromPointAtChartsSpace (point, container, coordinateSystem) { - point = container.frame.unframeThisPoint(point) - point = coordinateSystem.unInverseTransform(point, container.frame.height) - - let date = new Date(point.x) - - return date -} - -function getDateFromPointAtContainer (point, container, coordinateSystem) { - point = coordinateSystem.unInverseTransform(point, container.frame.height) - - let date = new Date(point.x) - - return date -} - -function getRateFromPointAtBrowserCanvas (point, container, coordinateSystem) { - point = unTransformThisPoint(point, container) - point = coordinateSystem.unInverseTransform(point, container.frame.height) - - return point.y -} - -function getRateFromPointAtChartingSpace (point, container, coordinateSystem) { - point = container.frame.unframeThisPoint(point) - point = coordinateSystem.unInverseTransform(point, container.frame.height) - - return point.y -} - -function getRateFromPointAtContainer (point, container, coordinateSystem) { - point = coordinateSystem.unInverseTransform(point, container.frame.height) - - return point.y -} - -function getMilisecondsFromPoint (point, container, coordinateSystem) { - point = unTransformThisPoint(point, container) - point = coordinateSystem.unInverseTransform(point, container.frame.height) - - return point.x -} - function moveToUserPosition (container, currentDate, currentRate, coordinateSystem, ignoreX, ignoreY, mousePosition, fitFunction) { let targetPoint = { x: currentDate.valueOf(), @@ -276,50 +82,6 @@ function removeTime (datetime) { return dateOnly } -function printLabel (labelToPrint, x, y, opacity, fontSize, color, center, container, fitFunction, noDecimals, fixedDecimals) { - if (labelToPrint === undefined) { return } - let labelPoint - if (color === undefined) { color = UI_COLOR.DARK } - if (fontSize === undefined) { fontSize = 10 }; - - browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY - let label = labelToPrint - if (isNaN(label) === false && label !== '') { - if (fixedDecimals !== true) { - label = dynamicDecimals(labelToPrint) - } - label = label.toLocaleString() - if (noDecimals === true) { - label = Math.trunc(label) - } - } - - let xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO - - if (center === true) { - labelPoint = { - x: x - xOffset, - y: y - } - } else { - labelPoint = { - x: x, - y: y - } - } - - if (container !== undefined) { - labelPoint = container.frame.frameThisPoint(labelPoint) - } - - if (fitFunction !== undefined) { - labelPoint = fitFunction(labelPoint) - } - - browserCanvasContext.fillStyle = 'rgba(' + color + ', ' + opacity + ')' - browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) -} - function newUniqueId () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8) diff --git a/CanvasApp/Utilities/CoordinateTransformations.js b/CanvasApp/Utilities/CoordinateTransformations.js new file mode 100644 index 000000000..9f3f9444d --- /dev/null +++ b/CanvasApp/Utilities/CoordinateTransformations.js @@ -0,0 +1,18 @@ +function transformThisPoint (point, container) { + /* We make the point relative to the current frame */ + + point = container.frame.frameThisPoint(point) + + /* We pass this point through the canvas.chartingSpace.viewport lends, meaning we apply the canvas.chartingSpace.viewport zoom and displacement. */ + + point = canvas.chartingSpace.viewport.transformThisPoint(point) + + return point +} + +function unTransformThisPoint (point, container) { + point = canvas.chartingSpace.viewport.unTransformThisPoint(point) + point = container.frame.unframeThisPoint(point) + + return point +} diff --git a/CanvasApp/Utilities/DateRateTransformations.js b/CanvasApp/Utilities/DateRateTransformations.js new file mode 100644 index 000000000..196c99ecc --- /dev/null +++ b/CanvasApp/Utilities/DateRateTransformations.js @@ -0,0 +1,53 @@ +function getDateFromPointAtBrowserCanvas (point, container, coordinateSystem) { + point = unTransformThisPoint(point, container) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + let date = new Date(point.x) + + return date +} + +function getDateFromPointAtChartsSpace (point, container, coordinateSystem) { + point = container.frame.unframeThisPoint(point) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + let date = new Date(point.x) + + return date +} + +function getDateFromPointAtContainer (point, container, coordinateSystem) { + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + let date = new Date(point.x) + + return date +} + +function getRateFromPointAtBrowserCanvas (point, container, coordinateSystem) { + point = unTransformThisPoint(point, container) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.y +} + +function getRateFromPointAtChartingSpace (point, container, coordinateSystem) { + point = container.frame.unframeThisPoint(point) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.y +} + +function getRateFromPointAtContainer (point, container, coordinateSystem) { + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.y +} + +function getMilisecondsFromPoint (point, container, coordinateSystem) { + point = unTransformThisPoint(point, container) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.x +} + diff --git a/CanvasApp/Utilities/Download.js b/CanvasApp/Utilities/Download.js new file mode 100644 index 000000000..39e8e3884 --- /dev/null +++ b/CanvasApp/Utilities/Download.js @@ -0,0 +1,58 @@ +function downloadText (filename, text) { + let element = document.createElement('a') + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)) + element.setAttribute('download', filename) + element.style.display = 'none' + document.body.appendChild(element) + element.click() + document.body.removeChild(element) +} + +function downloadCanvas (filename, canvasToUse) { + let data = canvasToUse.toDataURL('image/png', 1) + /* Change MIME type to trick the browser to downlaod the file instead of displaying it */ + data = data.replace(/^data:image\/[^;]*/, 'data:application/octet-stream') + + /* In addition to 's "download" attribute, you can define HTTP-style headers */ + data = data.replace(/^data:application\/octet-stream/, 'data:application/octet-stream;headers=Content-Disposition%3A%20attachment%3B%20filename=' + filename + '.png') + + let element = document.createElement('a') + element.setAttribute('href', data) + element.setAttribute('download', filename + '.PNG') + element.style.display = 'none' + document.body.appendChild(element) + element.click() + document.body.removeChild(element) +}; + +function downloadPanorama (filename) { + let INITIAL_WIDTH = 800 + let FINAL_WIDTH = browserCanvas.width - INITIAL_WIDTH + + if (ARE_WE_RECORDING_A_MARKET_PANORAMA === false) { + /* We start recording the panorame. */ + ARE_WE_RECORDING_A_MARKET_PANORAMA = true + marketPanoramaCanvas = document.createElement('canvas') + marketPanoramaCanvas.width = INITIAL_WIDTH * 50 + marketPanoramaCanvas.height = browserCanvas.height + marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, 0, 0, INITIAL_WIDTH, browserCanvas.height, 0, 0, INITIAL_WIDTH, browserCanvas.height) + CURRENT_PANORAMA_POSITION = INITIAL_WIDTH + } else { + /* We stop recording and download the resulting image. */ + ARE_WE_RECORDING_A_MARKET_PANORAMA = false + marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, browserCanvas.width - FINAL_WIDTH, 0, FINAL_WIDTH, browserCanvas.height, CURRENT_PANORAMA_POSITION, 0, FINAL_WIDTH, browserCanvas.height) + let finalResultCanvas = document.createElement('canvas') + finalResultCanvas.width = CURRENT_PANORAMA_POSITION + FINAL_WIDTH + finalResultCanvas.height = browserCanvas.height + finalResultCanvas.getContext('2d').drawImage(marketPanoramaCanvas, 0, 0, finalResultCanvas.width, browserCanvas.height, 0, 0, finalResultCanvas.width, browserCanvas.height) + downloadCanvas(filename, finalResultCanvas) + marketPanoramaCanvas = undefined + } +} + +function addToMarketPanorama () { + let SECTION_WIDTH = 80 * 2 + let INITIAL_POSITION = 800 - SECTION_WIDTH + marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, INITIAL_POSITION, 0, SECTION_WIDTH, browserCanvas.height, CURRENT_PANORAMA_POSITION, 0, SECTION_WIDTH, browserCanvas.height) + CURRENT_PANORAMA_POSITION = CURRENT_PANORAMA_POSITION + SECTION_WIDTH +} diff --git a/CanvasApp/Utilities/RoundedCornersBackground.js b/CanvasApp/Utilities/DrawPrint.js similarity index 77% rename from CanvasApp/Utilities/RoundedCornersBackground.js rename to CanvasApp/Utilities/DrawPrint.js index 825400009..4e38f2a0c 100644 --- a/CanvasApp/Utilities/RoundedCornersBackground.js +++ b/CanvasApp/Utilities/DrawPrint.js @@ -150,3 +150,72 @@ function drawIcon (icon, xFactor, yFactor, xOffset, yOffset, imageSize, containe } } } + +function drawContainerBackground (container, color, opacity, fitFunction) { + let fromPoint = { + x: 0, + y: 0 + } + + let toPoint = { + x: container.frame.width, + y: container.frame.height + } + + fromPoint = transformThisPoint(fromPoint, container) + toPoint = transformThisPoint(toPoint, container) + + fromPoint = fitFunction(fromPoint) + toPoint = fitFunction(toPoint) + + browserCanvasContext.beginPath() + browserCanvasContext.rect(fromPoint.x, fromPoint.y, toPoint.x - fromPoint.x, toPoint.y - fromPoint.y) + browserCanvasContext.fillStyle = 'rgba(' + color + ', ' + opacity + ')' + browserCanvasContext.closePath() + browserCanvasContext.fill() +} + +function printLabel (labelToPrint, x, y, opacity, fontSize, color, center, container, fitFunction, noDecimals, fixedDecimals) { + if (labelToPrint === undefined) { return } + let labelPoint + if (color === undefined) { color = UI_COLOR.DARK } + if (fontSize === undefined) { fontSize = 10 }; + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + let label = labelToPrint + if (isNaN(label) === false && label !== '') { + if (fixedDecimals !== true) { + label = dynamicDecimals(labelToPrint) + } + label = label.toLocaleString() + if (noDecimals === true) { + label = Math.trunc(label) + } + } + + let xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO + + if (center === true) { + labelPoint = { + x: x - xOffset, + y: y + } + } else { + labelPoint = { + x: x, + y: y + } + } + + if (container !== undefined) { + labelPoint = container.frame.frameThisPoint(labelPoint) + } + + if (fitFunction !== undefined) { + labelPoint = fitFunction(labelPoint) + } + + browserCanvasContext.fillStyle = 'rgba(' + color + ', ' + opacity + ')' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) +} + diff --git a/CanvasApp/Utilities/HiriarchyMap.js b/CanvasApp/Utilities/HiriarchyMap.js new file mode 100644 index 000000000..e27ec1614 --- /dev/null +++ b/CanvasApp/Utilities/HiriarchyMap.js @@ -0,0 +1,47 @@ +/* +This function scans a node hiriatchy recursively and creates a Map by Id of all the nodes of it. +*/ + +function getHiriarchyMap (rootNode) { + let hiriatchyMap = new Map() + evalNode(rootNode) + return hiriatchyMap + + function evalNode (node) { + if (node === undefined) { return } + + hiriatchyMap.set(node.id, node) + + /* Now we go down through all this node children */ + let nodeDefinition = APP_SCHEMA_MAP.get(node.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + evalNode(node[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + for (let m = 0; m < nodePropertyArray.length; m++) { + evalNode(nodePropertyArray[m]) + } + } + break + } + } + } + } + } +} diff --git a/CanvasApp/Utilities/LoadSaveFrame.js b/CanvasApp/Utilities/LoadSaveFrame.js new file mode 100644 index 000000000..5bda8c98b --- /dev/null +++ b/CanvasApp/Utilities/LoadSaveFrame.js @@ -0,0 +1,24 @@ +function saveFrame (payload, frame) { + payload.frame = { + position: { + x: frame.position.x, + y: frame.position.y + }, + width: frame.width, + height: frame.height, + radius: frame.radius + } +} + +function loadFrame (payload, frame) { + if (payload.node.savedPayload !== undefined) { + if (payload.node.savedPayload.frame !== undefined) { + frame.position.x = payload.node.savedPayload.frame.position.x + frame.position.y = payload.node.savedPayload.frame.position.y + frame.width = payload.node.savedPayload.frame.width + frame.height = payload.node.savedPayload.frame.height + frame.radius = payload.node.savedPayload.frame.radius + payload.node.savedPayload.frame = undefined + } + } +} diff --git a/CanvasApp/Utilities/NodeConfig.js b/CanvasApp/Utilities/NodeConfig.js new file mode 100644 index 000000000..482e1ae3e --- /dev/null +++ b/CanvasApp/Utilities/NodeConfig.js @@ -0,0 +1,18 @@ +function savePropertyAtNodeConfig (payload, propertyName, value) { + try { + let config = JSON.parse(payload.node.config) + config[propertyName] = value + payload.node.config = JSON.stringify(config, null, 4) + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } +} + +function loadPropertyFromNodeConfig (payload, propertyName) { + try { + let config = JSON.parse(payload.node.config) + return config[propertyName] + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } +} diff --git a/Files-Component/src/DailyFiles.js b/Files-Component/src/DailyFiles.js index 55cb27de9..d84a67d97 100644 --- a/Files-Component/src/DailyFiles.js +++ b/Files-Component/src/DailyFiles.js @@ -55,7 +55,7 @@ function newDailyFiles () { fileCloud.initialize(pBot, pHost, pPort) /* Some Validations */ - if (pDataset.code.validTimeFrames === undefined) { + if (pDataset.config.validTimeFrames === undefined) { if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = Can not initialize Market Files for bot ' + pBot.name) } if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = You need to define validTimeFrames at the Dataset config. ') } callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) @@ -111,7 +111,7 @@ function newDailyFiles () { let periodTime = dailyFilePeriods[i][0] let periodName = dailyFilePeriods[i][1] - if (pDataset.code.validTimeFrames.includes(periodName) === true) { + if (pDataset.config.validTimeFrames.includes(periodName) === true) { let fileCursor = newFileCursor() fileCursor.eventHandler = thisObject.eventHandler // We share our event handler with each file cursor, so that they can raise events there when files are changed.s fileCursor.initialize(fileCloud, pDataMine, pBot, pSession, pProduct, pDataset, exchange, pMarket, periodName, periodTime, pDatetime, pTimeFrame, beginDateRange, endDateRange, pEventsServerClient, onInitialized) @@ -147,7 +147,7 @@ function newDailyFiles () { let periodTime = dailyFilePeriods[i][0] let periodName = dailyFilePeriods[i][1] - if (pDataset.code.validTimeFrames.includes(periodName) === true) { + if (pDataset.config.validTimeFrames.includes(periodName) === true) { let fileCursor = fileCursors.get(periodTime) fileCursor.reload(onFileReceived) } diff --git a/Files-Component/src/FileCloud.js b/Files-Component/src/FileCloud.js index 051705797..69dfac0d8 100644 --- a/Files-Component/src/FileCloud.js +++ b/Files-Component/src/FileCloud.js @@ -36,18 +36,18 @@ function newFileCloud () { function getFileRecursively (pRetryCounter, pDataMine, pBot, pSession, pProduct, pDataset, pExchange, pMarket, pPeriodName, pDatetime, pSequence, pDataRange, callBackFunction) { try { if (INFO_LOG === true) { logger.write('[INFO] getFile -> getFileRecursively -> Entering function.') } - if (INFO_LOG === true) { logger.write('[INFO] getFile -> getFileRecursively -> key = ' + pDataMine.code.codeName + '-' + pBot.code.codeName + '-' + pDataset.code.filePath + '-' + pDataset.code.fileName) } + if (INFO_LOG === true) { logger.write('[INFO] getFile -> getFileRecursively -> key = ' + pDataMine.config.codeName + '-' + pBot.config.codeName + '-' + pDataset.config.filePath + '-' + pDataset.config.fileName) } let fileName let filePath if (pDataRange === undefined) { - fileName = pDataset.code.fileName - filePath = pDataset.code.filePath + fileName = pDataset.config.fileName + filePath = pDataset.config.filePath } else { - if (pDataset.code.dataRange !== undefined) { - fileName = pDataset.code.dataRange.fileName - filePath = pDataset.code.dataRange.filePath + if (pDataset.config.dataRange !== undefined) { + fileName = pDataset.config.dataRange.fileName + filePath = pDataset.config.dataRange.filePath } else { let customErr = { result: GLOBAL.CUSTOM_FAIL_RESPONSE.result, @@ -80,19 +80,19 @@ function newFileCloud () { } if (pDataMine !== undefined) { - filePath = filePath.replace('@DataMine', pDataMine.code.codeName) + filePath = filePath.replace('@DataMine', pDataMine.config.codeName) } if (pBot !== undefined) { - filePath = filePath.replace('@Bot', pBot.code.codeName) + filePath = filePath.replace('@Bot', pBot.config.codeName) } if (pSession !== undefined) { - let code + let config let sessionFolderName = pSession.id - if (pSession.code !== undefined) { - if (pSession.code.folderName !== undefined) { - sessionFolderName = pSession.code.folderName + '-' + pSession.id + if (pSession.config !== undefined) { + if (pSession.config.folderName !== undefined) { + sessionFolderName = pSession.config.folderName + '-' + pSession.id } } filePath = filePath.replace('@Session', sessionFolderName) @@ -103,8 +103,8 @@ function newFileCloud () { } filePath = filePath.replace('@Period', pPeriodName) - filePath = filePath.replace('@Dataset', pDataset.code.codeName) - filePath = filePath.replace('@Product', pProduct.code.codeName) + filePath = filePath.replace('@Dataset', pDataset.config.codeName) + filePath = filePath.replace('@Product', pProduct.config.codeName) if (pDatetime !== undefined) { filePath = filePath.replace('@Year', pDatetime.getUTCFullYear()) diff --git a/Files-Component/src/FileCursor.js b/Files-Component/src/FileCursor.js index 29ddb900e..85f3bcc8c 100644 --- a/Files-Component/src/FileCursor.js +++ b/Files-Component/src/FileCursor.js @@ -108,11 +108,11 @@ function newFileCursor () { endDateRange = pEndDateRange eventsServerClient = pEventsServerClient - let key = dataMine.code.codeName + '-' + bot.code.codeName + '-' + product.code.codeName + '-' + dataset.code.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset + let key = dataMine.config.codeName + '-' + bot.config.codeName + '-' + product.config.codeName + '-' + dataset.config.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset let callerId = key + '-' + periodName + newUniqueId() eventsServerClient.listenToEvent(key, 'Dataset Updated', undefined, callerId, onResponseDataSet, updateFiles) - key = dataMine.code.codeName + '-' + bot.code.codeName + '-' + product.code.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset + key = dataMine.config.codeName + '-' + bot.config.codeName + '-' + product.config.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset callerId = key + '-' + periodName + newUniqueId() eventsServerClient.listenToEvent(key, 'Data Range Updated', undefined, callerId, onResponseDataRange, updateDataRange) diff --git a/Files-Component/src/FileSequence.js b/Files-Component/src/FileSequence.js index f14de19f4..187576503 100644 --- a/Files-Component/src/FileSequence.js +++ b/Files-Component/src/FileSequence.js @@ -88,7 +88,7 @@ function newFileSequence () { callerId = newUniqueId() - let key = dataMine.code.codeName + '-' + bot.code.codeName + '-' + product.code.codeName + '-' + dataset.code.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset + let key = dataMine.config.codeName + '-' + bot.config.codeName + '-' + product.config.codeName + '-' + dataset.config.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset eventsServerClient.listenToEvent(key, 'Dataset Updated', undefined, callerId, onResponse, updateFiles) function onResponse (message) { diff --git a/Files-Component/src/MarketFiles.js b/Files-Component/src/MarketFiles.js index 666389d08..a6b0187bb 100644 --- a/Files-Component/src/MarketFiles.js +++ b/Files-Component/src/MarketFiles.js @@ -95,7 +95,7 @@ function newMarketFiles () { fileCloud.initialize(pBot, pHost, pPort) /* Some Validations */ - if (dataset.code.validTimeFrames === undefined) { + if (dataset.config.validTimeFrames === undefined) { if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = Can not initialize Market Files for bot ' + pBot.name) } if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = You need to define validTimeFrames at the Dataset config. ') } callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) @@ -108,7 +108,7 @@ function newMarketFiles () { let periodTime = marketFilesPeriods[i][0] let periodName = marketFilesPeriods[i][1] - if (dataset.code.validTimeFrames.includes(periodName) === true) { + if (dataset.config.validTimeFrames.includes(periodName) === true) { fileCloud.getFile(dataMine, bot, session, product, dataset, exchange, market, periodName, undefined, undefined, undefined, onFileReceived) function onFileReceived (err, file) { @@ -122,7 +122,7 @@ function newMarketFiles () { } if (filesLoaded + filesNotLoaded === marketFilesPeriods.length) { - let key = dataMine.code.codeName + '-' + bot.code.codeName + '-' + product.code.codeName + '-' + dataset.code.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset + let key = dataMine.config.codeName + '-' + bot.config.codeName + '-' + product.config.codeName + '-' + dataset.config.codeName + '-' + exchange.name + '-' + market.baseAsset + '/' + market.quotedAsset let callerId = key + '-' + periodName + newUniqueId() eventsServerClient.listenToEvent(key, 'Dataset Updated', undefined, callerId, onResponse, updateFiles) @@ -156,7 +156,7 @@ function newMarketFiles () { let periodTime = marketFilesPeriods[i][0] let periodName = marketFilesPeriods[i][1] - if (dataset.code.validTimeFrames.includes(periodName) === true) { + if (dataset.config.validTimeFrames.includes(periodName) === true) { fileCloud.getFile(dataMine, bot, session, product, dataset, exchange, market, periodName, undefined, undefined, undefined, onFileReceived) function onFileReceived (err, file) { diff --git a/Files-Component/src/SingleFile.js b/Files-Component/src/SingleFile.js index 17d2542de..9a6fdbae8 100644 --- a/Files-Component/src/SingleFile.js +++ b/Files-Component/src/SingleFile.js @@ -30,7 +30,7 @@ function newSingleFile () { callBackFunction) { try { if (INFO_LOG === true) { logger.write('[INFO] initialize -> Entering function.') } - if (INFO_LOG === true) { logger.write('[INFO] initialize -> key = ' + pDataMine.code.codeName + '-' + pBot.code.codeName + '-' + pProduct.code.codeName) } + if (INFO_LOG === true) { logger.write('[INFO] initialize -> key = ' + pDataMine.config.codeName + '-' + pBot.config.codeName + '-' + pProduct.config.codeName) } let exchange = pExchange diff --git a/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Daily/User.Bot.js b/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Daily/User.Bot.js index 8c27945fd..08dc3de97 100644 --- a/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Daily/User.Bot.js +++ b/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Daily/User.Bot.js @@ -88,7 +88,7 @@ /* We look first for Exchange Raw Data in order to get when the market starts. */ - reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -124,7 +124,7 @@ /* Second, we get the report from Candles Volumes, to know when the marted ends. */ - reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Daily" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -159,7 +159,7 @@ /* Finally we get our own Status Report. */ - reportKey = "Masters" + "-" + "Bollinger-Bands" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Bollinger-Bands" + "-" + "Multi-Period-Daily" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -868,7 +868,7 @@ try { - let reportKey = "Masters" + "-" + "Bollinger-Bands" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + let reportKey = "Masters" + "-" + "Bollinger-Bands" + "-" + "Multi-Period-Daily" let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.processDatetime; diff --git a/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Market/User.Bot.js b/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Market/User.Bot.js index f5bd3cb5f..820424630 100644 --- a/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Market/User.Bot.js +++ b/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/Multi-Period-Market/User.Bot.js @@ -465,7 +465,7 @@ try { - let reportKey = "Masters" + "-" + "Bollinger-Bands" + "-" + "Multi-Period-Market" + "-" + "dataSet.V1"; + let reportKey = "Masters" + "-" + "Bollinger-Bands" + "-" + "Multi-Period-Market" let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.processDatetime; diff --git a/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/this.bot.config.json b/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/this.bot.config.json index 3b08b5895..547ac0a0e 100644 --- a/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/this.bot.config.json +++ b/Legacy-Code/Masters/bots/Bollinger-Bands-Indicator-Bot/this.bot.config.json @@ -9,7 +9,6 @@ }, "dataMine": "Masters", "profilePicture": "Bollinger Bands.png", - "dataSetVersion": "dataSet.V1", "processes": [ { "name": "Multi-Period-Market", @@ -52,22 +51,12 @@ { "dataMine": "Masters", "bot": "Bollinger-Bands", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Multi-Period-Market", - "dataSetVersion": "dataSet.V1" + "process": "Multi-Period-Market" }, { "dataMine": "Masters", "bot": "Candles-Volumes", - "botVersion": { - "major": 1, - "minor": 0 - }, "process": "Multi-Period-Market", - "dataSetVersion": "dataSet.V1", "waitUntilNextUpdate": true } ] @@ -113,33 +102,18 @@ { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Historic-Trades", - "dataSetVersion": "dataSet.V1" + "process": "Historic-Trades" }, { "dataMine": "Masters", "bot": "Candles-Volumes", - "botVersion": { - "major": 1, - "minor": 0 - }, "process": "Multi-Period-Daily", - "dataSetVersion": "dataSet.V1", "waitUntilNextUpdate": true }, { "dataMine": "Masters", "bot": "Bollinger-Bands", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Multi-Period-Daily", - "dataSetVersion": "dataSet.V1" + "process": "Multi-Period-Daily" } ] } diff --git a/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Daily/User.Bot.js b/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Daily/User.Bot.js index 7ddba27e8..2ba27516e 100644 --- a/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Daily/User.Bot.js +++ b/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Daily/User.Bot.js @@ -87,7 +87,7 @@ Read the candles and volumes from Exchange Raw Data and produce a file for each /* We look first for Exchange Raw Data in order to get when the market starts. */ - reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -123,7 +123,7 @@ Read the candles and volumes from Exchange Raw Data and produce a file for each /* Second, we get the report from Exchange Raw Data, to know when the marted ends. */ - reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" + "-" + "dataSet.V1" + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -158,7 +158,7 @@ Read the candles and volumes from Exchange Raw Data and produce a file for each /* Finally we get our own Status Report. */ - reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Daily" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -786,7 +786,7 @@ Read the candles and volumes from Exchange Raw Data and produce a file for each try { - let reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + let reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Daily" let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.processDatetime; diff --git a/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Market/User.Bot.js b/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Market/User.Bot.js index 2fb83a2fe..1b1055a6a 100644 --- a/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Market/User.Bot.js +++ b/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/Multi-Period-Market/User.Bot.js @@ -96,7 +96,7 @@ /* We look first for Exchange Raw Data in order to get when the market starts. */ - reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -132,7 +132,7 @@ /* Second, we get the report from Exchange Raw Data, to know when the marted ends. */ - reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" + "-" + "dataSet.V1" + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -167,7 +167,7 @@ /* Finally we get our own Status Report. */ - reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Market" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Market" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } statusReport = statusDependencies.statusReports.get(reportKey); @@ -904,7 +904,7 @@ try { - let reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Market" + "-" + "dataSet.V1"; + let reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Market" let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.processDatetime; diff --git a/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/this.bot.config.json b/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/this.bot.config.json index b1cc979c9..aa1100164 100644 --- a/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/this.bot.config.json +++ b/Legacy-Code/Masters/bots/Candles-Volumes-Indicator-Bot/this.bot.config.json @@ -2,14 +2,7 @@ "displayName": "Candles Volumes", "codeName": "Candles-Volumes", "type": "Indicator Bot", - "version": { - "major": 1, - "minor": 0, - "patch": 0 - }, "dataMine": "Masters", - "profilePicture": "Candles Volumes.png", - "dataSetVersion": "dataSet.V1", "processes": [ { "name": "Multi-Period-Market", @@ -52,34 +45,19 @@ { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Historic-OHLCVs", - "dataSetVersion": "dataSet.V1" + "process": "Historic-OHLCVs" }, { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, "process": "Historic-OHLCVs", - "dataSetVersion": "dataSet.V1", "dataSetSection": "Month", "waitUntilNextUpdate": true }, { "dataMine": "Masters", "bot": "Candles-Volumes", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Multi-Period-Market", - "dataSetVersion": "dataSet.V1" + "process": "Multi-Period-Market" } ] }, @@ -124,34 +102,19 @@ { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Historic-OHLCVs", - "dataSetVersion": "dataSet.V1" + "process": "Historic-OHLCVs" }, { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, "process": "Historic-OHLCVs", - "dataSetVersion": "dataSet.V1", "dataSetSection": "Month", "waitUntilNextUpdate": true }, { "dataMine": "Masters", "bot": "Candles-Volumes", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Multi-Period-Daily", - "dataSetVersion": "dataSet.V1" + "process": "Multi-Period-Daily" } ] } diff --git a/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-OHLCVs/User.Bot.js b/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-OHLCVs/User.Bot.js index 7f73303f2..75f332fca 100644 --- a/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-OHLCVs/User.Bot.js +++ b/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-OHLCVs/User.Bot.js @@ -57,35 +57,35 @@ exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, FILE_S exchangeId = bot.exchange.toLowerCase() /* Applying the parameters defined by the user at the Exchange Node Config */ - if (bot.exchangeNode.code.API !== undefined) { - for (let i = 0; i < bot.exchangeNode.code.API.length; i++) { - if (bot.exchangeNode.code.API[i].method === 'fetch_ohlcv') { - if (bot.exchangeNode.code.API[i].class !== undefined) { - exchangeId = bot.exchangeNode.code.API[i].class + if (bot.exchangeNode.config.API !== undefined) { + for (let i = 0; i < bot.exchangeNode.config.API.length; i++) { + if (bot.exchangeNode.config.API[i].method === 'fetch_ohlcv') { + if (bot.exchangeNode.config.API[i].class !== undefined) { + exchangeId = bot.exchangeNode.config.API[i].class } - if (bot.exchangeNode.code.API[i].fetchOHLCVsMethod !== undefined) { + if (bot.exchangeNode.config.API[i].fetchOHLCVsMethod !== undefined) { options = { - 'fetchOHLCVsMethod': bot.exchangeNode.code.API[i].fetchOHLCVsMethod + 'fetchOHLCVsMethod': bot.exchangeNode.config.API[i].fetchOHLCVsMethod } } - if (bot.exchangeNode.code.API[i].firstId !== undefined) { - firstId = bot.exchangeNode.code.API[i].firstId + if (bot.exchangeNode.config.API[i].firstId !== undefined) { + firstId = bot.exchangeNode.config.API[i].firstId } - if (bot.exchangeNode.code.API[i].rateLimit !== undefined) { - rateLimit = bot.exchangeNode.code.API[i].rateLimit + if (bot.exchangeNode.config.API[i].rateLimit !== undefined) { + rateLimit = bot.exchangeNode.config.API[i].rateLimit } - if (bot.exchangeNode.code.API[i].hostname !== undefined) { - hostname = bot.exchangeNode.code.API[i].hostname + if (bot.exchangeNode.config.API[i].hostname !== undefined) { + hostname = bot.exchangeNode.config.API[i].hostname } - if (bot.exchangeNode.code.API[i].fetchType !== undefined) { - fetchType = bot.exchangeNode.code.API[i].fetchType + if (bot.exchangeNode.config.API[i].fetchType !== undefined) { + fetchType = bot.exchangeNode.config.API[i].fetchType } } } } - if (bot.code.fetchLimit !== undefined) { - limit = bot.code.fetchLimit + if (bot.config.fetchLimit !== undefined) { + limit = bot.config.fetchLimit } let key = process.env.KEY @@ -151,7 +151,7 @@ exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, FILE_S try { let reportKey; - reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } if (statusDependencies.statusReports.get(reportKey).status === "Status Report is corrupt.") { diff --git a/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-Trades/User.Bot.js b/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-Trades/User.Bot.js index 3b5ddff99..dfa816cf6 100644 --- a/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-Trades/User.Bot.js +++ b/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/Historic-Trades/User.Bot.js @@ -52,25 +52,25 @@ exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, FILE_S exchangeId = bot.exchange.toLowerCase() /* Applying the parameters defined by the user at the Exchange node */ - if (bot.exchangeNode.code.API !== undefined) { - for (let i = 0; i < bot.exchangeNode.code.API.length; i++) { - if (bot.exchangeNode.code.API[i].method === 'fetchTrades') { - if (bot.exchangeNode.code.API[i].class !== undefined) { - exchangeId = bot.exchangeNode.code.API[i].class + if (bot.exchangeNode.config.API !== undefined) { + for (let i = 0; i < bot.exchangeNode.config.API.length; i++) { + if (bot.exchangeNode.config.API[i].method === 'fetchTrades') { + if (bot.exchangeNode.config.API[i].class !== undefined) { + exchangeId = bot.exchangeNode.config.API[i].class } - if (bot.exchangeNode.code.API[i].fetchType !== undefined) { - fetchType = bot.exchangeNode.code.API[i].fetchType + if (bot.exchangeNode.config.API[i].fetchType !== undefined) { + fetchType = bot.exchangeNode.config.API[i].fetchType } - if (bot.exchangeNode.code.API[i].fetchTradesMethod !== undefined) { + if (bot.exchangeNode.config.API[i].fetchTradesMethod !== undefined) { options = { - 'fetchTradesMethod': bot.exchangeNode.code.API[i].fetchTradesMethod + 'fetchTradesMethod': bot.exchangeNode.config.API[i].fetchTradesMethod } } - if (bot.exchangeNode.code.API[i].firstId !== undefined) { - firstId = bot.exchangeNode.code.API[i].firstId + if (bot.exchangeNode.config.API[i].firstId !== undefined) { + firstId = bot.exchangeNode.config.API[i].firstId } - if (bot.exchangeNode.code.API[i].rateLimit !== undefined) { - rateLimit = bot.exchangeNode.code.API[i].rateLimit + if (bot.exchangeNode.config.API[i].rateLimit !== undefined) { + rateLimit = bot.exchangeNode.config.API[i].rateLimit } } } @@ -136,7 +136,7 @@ exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, FILE_S try { let reportKey; - reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-Trades" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-Trades" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } if (statusDependencies.statusReports.get(reportKey).status === "Status Report is corrupt.") { diff --git a/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/this.bot.config.json b/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/this.bot.config.json index c2ca69340..fcfcc6eaa 100644 --- a/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/this.bot.config.json +++ b/Legacy-Code/Masters/bots/Exchange-Raw-Data-Sensor-Bot/this.bot.config.json @@ -2,14 +2,8 @@ "displayName": "Exchange Raw Data", "codeName": "Exchange-Raw-Data", "type": "Sensor Bot", - "version": { - "major": 1, - "minor": 0, - "patch": 0 - }, "dataMine": "Masters", "profilePicture": "Exchange Raw Data.png", - "dataSetVersion": "dataSet.V1", "processes": [ { "name": "Live-Trades", @@ -42,12 +36,7 @@ { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Live-Trades", - "dataSetVersion": "dataSet.V1" + "process": "Live-Trades" } ] }, @@ -82,22 +71,12 @@ { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Live-Trades", - "dataSetVersion": "dataSet.V1" + "process": "Live-Trades" }, { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Historic-Trades", - "dataSetVersion": "dataSet.V1" + "process": "Historic-Trades" } ] }, @@ -132,44 +111,24 @@ { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, "process": "Live-Trades", - "dataSetVersion": "dataSet.V1", "waitUntilNextUpdate": true }, { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Historic-Trades", - "dataSetVersion": "dataSet.V1" + "process": "Historic-Trades" }, { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, "process": "Hole-Fixing", - "dataSetVersion": "dataSet.V1", "dataSetSection": "Month" }, { "dataMine": "Masters", "bot": "Exchange-Raw-Data", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Hole-Fixing", - "dataSetVersion": "dataSet.V1" + "process": "Hole-Fixing" } ] } diff --git a/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/Check-Webhook/User.Bot.js b/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/Check-Webhook/User.Bot.js index f447c8b54..4037cb4ca 100644 --- a/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/Check-Webhook/User.Bot.js +++ b/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/Check-Webhook/User.Bot.js @@ -50,7 +50,7 @@ exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, FILE_S try { let reportKey; - reportKey = "Masters" + "-" + "Webhooks" + "-" + "Check-Webhook" + "-" + "dataSet.V1"; + reportKey = "Masters" + "-" + "Webhooks" + "-" + "Check-Webhook" if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } diff --git a/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/this.bot.config.json b/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/this.bot.config.json index bf9b802bd..223ff23e8 100644 --- a/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/this.bot.config.json +++ b/Legacy-Code/Masters/bots/Webhooks-Sensor-Bot/this.bot.config.json @@ -8,7 +8,6 @@ "patch": 0 }, "dataMine": "Masters", - "dataSetVersion": "dataSet.V1", "processes": [ { "name": "Check-Webhook", @@ -41,12 +40,7 @@ { "dataMine": "Masters", "bot": "Webhooks", - "botVersion": { - "major": 1, - "minor": 0 - }, - "process": "Check-Webhook", - "dataSetVersion": "dataSet.V1" + "process": "Check-Webhook" } ] } diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Conditions.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Conditions.js index a9eb8a1c8..1d932cf6e 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Conditions.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Conditions.js @@ -25,7 +25,7 @@ // Secondary functions and properties. - currentRecord: undefined + record: undefined }; /* this is part of the module template */ @@ -374,13 +374,12 @@ record.begin = dailyFile[1][i][0]; record.end = dailyFile[1][i][1]; - record.strategyNumber = dailyFile[1][i][2]; - record.strategyPhase = dailyFile[1][i][3]; - record.stopLossPhase = dailyFile[1][i][4]; - record.takeProfitPhase = dailyFile[1][i][5]; - record.conditions = dailyFile[1][i][6]; - record.formulaErrors = dailyFile[1][i][7]; - record.formulaValues = dailyFile[1][i][8]; + record.strategyIndex = dailyFile[1][i][2]; + record.variable_current_position_stopLoss_phase = dailyFile[1][i][3]; + record.variable_current_position_takeProfit_phase = dailyFile[1][i][4]; + record.conditions = dailyFile[1][i][5]; + record.formulaErrors = dailyFile[1][i][6]; + record.formulaValues = dailyFile[1][i][7]; if ( (record.begin >= farLeftDate.valueOf() && record.end <= farRightDate.valueOf()) && @@ -391,8 +390,8 @@ if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Condition Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Condition Changed", thisObject.record); } } @@ -446,13 +445,12 @@ record.begin = marketFile[1][i][0]; record.end = marketFile[1][i][1]; - record.strategyNumber = marketFile[1][i][2]; - record.strategyPhase = marketFile[1][i][3]; - record.stopLossPhase = marketFile[1][i][4]; - record.takeProfitPhase = marketFile[1][i][5]; - record.conditions = marketFile[1][i][6]; - record.formulaErrors = marketFile[1][i][7]; - record.formulaValues = marketFile[1][i][8]; + record.strategyIndex = marketFile[1][i][2]; + record.variable_current_position_stopLoss_phase = marketFile[1][i][3]; + record.variable_current_position_takeProfit_phase = marketFile[1][i][4]; + record.conditions = marketFile[1][i][5]; + record.formulaErrors = marketFile[1][i][6]; + record.formulaValues = marketFile[1][i][7]; if ( (record.begin >= leftDate.valueOf() && record.end <= rightDate.valueOf()) && @@ -463,8 +461,8 @@ if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Condition Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Condition Changed", thisObject.record); } } @@ -494,18 +492,17 @@ if (userPositionDate >= conditionRecord.begin && userPositionDate <= conditionRecord.end) { - let currentRecord = { + let record = { conditionsNames: headers, - strategyNumber: conditionRecord.strategyNumber, - strategyPhase: conditionRecord.strategyPhase, - stopLossPhase: conditionRecord.stopLossPhase, - takeProfitPhase: conditionRecord.takeProfitPhase, + strategyIndex: conditionRecord.strategyIndex, + variable_current_position_stopLoss_phase: conditionRecord.variable_current_position_stopLoss_phase, + variable_current_position_takeProfit_phase: conditionRecord.variable_current_position_takeProfit_phase, conditionsValues: conditionRecord.conditions, formulaErrors: conditionRecord.formulaErrors, formulaValues: conditionRecord.formulaValues }; - sendRecordInfoToDesignerSpace(currentRecord) + sendRecordInfoToDesignerSpace(record) } } @@ -515,15 +512,15 @@ } } - function sendRecordInfoToDesignerSpace(currentRecord) { + function sendRecordInfoToDesignerSpace(record) { - if (currentRecord === undefined) { return; } - if (currentRecord.conditionsNames === undefined) { return; } + if (record === undefined) { return; } + if (record.conditionsNames === undefined) { return; } if (canvas.designSpace.workspace === undefined) { return; } /* We will get now the designer trading system */ let designerTradingSystem - let fileTradingSystem = currentRecord.conditionsNames; + let fileTradingSystem = record.conditionsNames; for (let i = 0; i < canvas.designSpace.workspace.workspaceNode.rootNodes.length; i++) { let rootNode = canvas.designSpace.workspace.workspaceNode.rootNodes[i] @@ -568,7 +565,7 @@ let strategy = fileTradingSystem.strategies[j]; designerTradingSystem.strategies[j].payload.uiObject.setErrorMessage(strategy.error) - if (currentRecord.strategyNumber === j) { + if (record.strategyIndex === j) { designerTradingSystem.strategies[j].payload.uiObject.isExecuting = true } else { designerTradingSystem.strategies[j].payload.uiObject.isExecuting = false @@ -653,7 +650,7 @@ for (let p = 0; p < initialDefinition.stopLoss.phases.length; p++) { - if (currentRecord.strategyNumber === j && currentRecord.stopLossPhase === p) { + if (record.strategyIndex === j && record.variable_current_position_stopLoss_phase === p) { designerTradingSystem.strategies[j].openStage.initialDefinition.stopLoss.phases[p].payload.uiObject.isExecuting = true } else { designerTradingSystem.strategies[j].openStage.initialDefinition.stopLoss.phases[p].payload.uiObject.isExecuting = false @@ -665,15 +662,15 @@ if (phase.formula !== undefined) { designerTradingSystem.strategies[j].openStage.initialDefinition.stopLoss.phases[p].formula.payload.uiObject.setErrorMessage(openStage.initialDefinition.stopLoss.phases[p].formula.error) - if (currentRecord.formulaErrors[formulaErrorsIndex] !== "") { - if (currentRecord.strategyNumber === j && currentRecord.stopLossPhase === p) { - designerTradingSystem.strategies[j].openStage.initialDefinition.stopLoss.phases[p].formula.payload.uiObject.setErrorMessage(currentRecord.formulaErrors[formulaErrorsIndex]) + if (record.formulaErrors[formulaErrorsIndex] !== "") { + if (record.strategyIndex === j && record.variable_current_position_stopLoss_phase === p) { + designerTradingSystem.strategies[j].openStage.initialDefinition.stopLoss.phases[p].formula.payload.uiObject.setErrorMessage(record.formulaErrors[formulaErrorsIndex]) } } formulaErrorsIndex++ - if (currentRecord.strategyNumber === j && currentRecord.stopLossPhase === p) { - designerTradingSystem.strategies[j].openStage.initialDefinition.stopLoss.phases[p].formula.payload.uiObject.setValue(currentRecord.formulaValues[formulaValuesIndex]) + if (record.strategyIndex === j && record.variable_current_position_stopLoss_phase === p) { + designerTradingSystem.strategies[j].openStage.initialDefinition.stopLoss.phases[p].formula.payload.uiObject.setValue(record.formulaValues[formulaValuesIndex]) } formulaValuesIndex++ } @@ -738,7 +735,7 @@ for (let p = 0; p < initialDefinition.takeProfit.phases.length; p++) { - if (currentRecord.strategyNumber === j && currentRecord.takeProfitPhase === p) { + if (record.strategyIndex === j && record.variable_current_position_takeProfit_phase === p) { designerTradingSystem.strategies[j].openStage.initialDefinition.takeProfit.phases[p].payload.uiObject.isExecuting = true } else { designerTradingSystem.strategies[j].openStage.initialDefinition.takeProfit.phases[p].payload.uiObject.isExecuting = false @@ -750,15 +747,15 @@ if (phase.formula !== undefined) { designerTradingSystem.strategies[j].openStage.initialDefinition.takeProfit.phases[p].formula.payload.uiObject.setErrorMessage(openStage.initialDefinition.takeProfit.phases[p].formula.error) - if (currentRecord.formulaErrors[formulaErrorsIndex] !== "") { - if (currentRecord.strategyNumber === j && currentRecord.takeProfitPhase === p) { - designerTradingSystem.strategies[j].openStage.initialDefinition.takeProfit.phases[p].formula.payload.uiObject.setErrorMessage(currentRecord.formulaErrors[formulaErrorsIndex]) + if (record.formulaErrors[formulaErrorsIndex] !== "") { + if (record.strategyIndex === j && record.variable_current_position_takeProfit_phase === p) { + designerTradingSystem.strategies[j].openStage.initialDefinition.takeProfit.phases[p].formula.payload.uiObject.setErrorMessage(record.formulaErrors[formulaErrorsIndex]) } } formulaErrorsIndex++ - if (currentRecord.strategyNumber === j && currentRecord.takeProfitPhase === p) { - designerTradingSystem.strategies[j].openStage.initialDefinition.takeProfit.phases[p].formula.payload.uiObject.setValue(currentRecord.formulaValues[formulaValuesIndex]) + if (record.strategyIndex === j && record.variable_current_position_takeProfit_phase === p) { + designerTradingSystem.strategies[j].openStage.initialDefinition.takeProfit.phases[p].formula.payload.uiObject.setValue(record.formulaValues[formulaValuesIndex]) } formulaValuesIndex++ } @@ -836,7 +833,7 @@ for (let p = 0; p < manageStage.stopLoss.phases.length; p++) { - if (currentRecord.strategyNumber === j && currentRecord.stopLossPhase - 1 === p) { + if (record.strategyIndex === j && record.variable_current_position_stopLoss_phase - 1 === p) { designerTradingSystem.strategies[j].manageStage.stopLoss.phases[p].payload.uiObject.isExecuting = true } else { designerTradingSystem.strategies[j].manageStage.stopLoss.phases[p].payload.uiObject.isExecuting = false @@ -848,14 +845,14 @@ if (phase.formula !== undefined) { designerTradingSystem.strategies[j].manageStage.stopLoss.phases[p].formula.payload.uiObject.setErrorMessage(manageStage.stopLoss.phases[p].formula.error) - if (currentRecord.formulaErrors[formulaErrorsIndex] !== "") { - if (currentRecord.strategyNumber === j && currentRecord.stopLossPhase - 1 === p) { - designerTradingSystem.strategies[j].manageStage.stopLoss.phases[p].formula.payload.uiObject.setErrorMessage(currentRecord.formulaErrors[formulaErrorsIndex]) + if (record.formulaErrors[formulaErrorsIndex] !== "") { + if (record.strategyIndex === j && record.variable_current_position_stopLoss_phase - 1 === p) { + designerTradingSystem.strategies[j].manageStage.stopLoss.phases[p].formula.payload.uiObject.setErrorMessage(record.formulaErrors[formulaErrorsIndex]) } } formulaErrorsIndex++ - if (currentRecord.strategyNumber === j && currentRecord.stopLossPhase - 1 === p) { - designerTradingSystem.strategies[j].manageStage.stopLoss.phases[p].formula.payload.uiObject.setValue(currentRecord.formulaValues[formulaValuesIndex]) + if (record.strategyIndex === j && record.variable_current_position_stopLoss_phase - 1 === p) { + designerTradingSystem.strategies[j].manageStage.stopLoss.phases[p].formula.payload.uiObject.setValue(record.formulaValues[formulaValuesIndex]) } formulaValuesIndex++ } @@ -920,7 +917,7 @@ for (let p = 0; p < manageStage.takeProfit.phases.length; p++) { - if (currentRecord.strategyNumber === j && currentRecord.takeProfitPhase - 1 === p) { + if (record.strategyIndex === j && record.variable_current_position_takeProfit_phase - 1 === p) { designerTradingSystem.strategies[j].manageStage.takeProfit.phases[p].payload.uiObject.isExecuting = true } else { designerTradingSystem.strategies[j].manageStage.takeProfit.phases[p].payload.uiObject.isExecuting = false @@ -932,15 +929,15 @@ if (phase.formula !== undefined) { designerTradingSystem.strategies[j].manageStage.takeProfit.phases[p].formula.payload.uiObject.setErrorMessage(manageStage.takeProfit.phases[p].formula.error) - if (currentRecord.formulaErrors[formulaErrorsIndex] !== "") { - if (currentRecord.strategyNumber === j && currentRecord.takeProfitPhase - 1 === p) { - designerTradingSystem.strategies[j].manageStage.takeProfit.phases[p].formula.payload.uiObject.setErrorMessage(currentRecord.formulaErrors[formulaErrorsIndex]) + if (record.formulaErrors[formulaErrorsIndex] !== "") { + if (record.strategyIndex === j && record.variable_current_position_takeProfit_phase - 1 === p) { + designerTradingSystem.strategies[j].manageStage.takeProfit.phases[p].formula.payload.uiObject.setErrorMessage(record.formulaErrors[formulaErrorsIndex]) } } formulaErrorsIndex++ - if (currentRecord.strategyNumber === j && currentRecord.takeProfitPhase - 1 === p) { - designerTradingSystem.strategies[j].manageStage.takeProfit.phases[p].formula.payload.uiObject.setValue(currentRecord.formulaValues[formulaValuesIndex]) + if (record.strategyIndex === j && record.variable_current_position_takeProfit_phase - 1 === p) { + designerTradingSystem.strategies[j].manageStage.takeProfit.phases[p].formula.payload.uiObject.setValue(record.formulaValues[formulaValuesIndex]) } formulaValuesIndex++ } @@ -1032,7 +1029,7 @@ } } - if (currentRecord.conditionsValues[conditionIndex] === 1) { + if (record.conditionsValues[conditionIndex] === 1) { condition.payload.uiObject.highlight() } else { highlightSituation = false diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/FinalResults.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/FinalResults.js deleted file mode 100644 index dcc9ce8d2..000000000 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/FinalResults.js +++ /dev/null @@ -1,348 +0,0 @@ -function newAAMastersPlottersTradingSimulationFinalResults() { - - const MODULE_NAME = "Final Results Plotter"; - const ERROR_LOG = true; - const INTENSIVE_LOG = false; - const logger = newWebDebugLog(); - logger.fileName = MODULE_NAME; - - let thisObject = { - - // Main functions and properties. - - initialize: initialize, - finalize: finalize, - container: undefined, - getContainer: getContainer, - setTimePeriod: setTimePeriod, - setDatetime: setDatetime, - draw: draw, - recalculateScale: recalculateScale, - currentRecord: undefined - }; - - /* this is part of the module template */ - - let container = newContainer(); // Do not touch this 3 lines, they are just needed. - container.initialize(); - thisObject.container = container; - - let timeLineCoordinateSystem = newTimeLineCoordinateSystem(); // Needed to be able to plot on the timeline, otherwise not. - - let timePeriod; // This will hold the current Time Period the user is at. - let datetime; // This will hold the current Datetime the user is at. - - let dataFile; // This is the current Market File being plotted. - let fileCursor; // This is the current File Cursor being used to retrieve Daily Files. - - let singleFile; // This object will provide the different Market Files at different Time Periods. - - /* these are module specific variables: */ - - let records = []; // Here we keep the records to be ploted every time the Draw() function is called by the AAWebPlatform. - - let imageStopLossPhase; - let imageTakeProfitPhase; - let imageRecord; - - let zoomChangedEventSubscriptionId - let offsetChangedEventSubscriptionId - let filesUpdatedEventSubscriptionId - let dragFinishedEventSubscriptionId - let dimmensionsChangedEventSubscriptionId - - return thisObject; - - function finalize() { - try { - - /* Stop listening to the necesary events. */ - - viewPort.eventHandler.stopListening(zoomChangedEventSubscriptionId); - viewPort.eventHandler.stopListening(offsetChangedEventSubscriptionId); - singleFile.eventHandler.stopListening(filesUpdatedEventSubscriptionId); - canvas.eventHandler.stopListening(dragFinishedEventSubscriptionId); - thisObject.container.eventHandler.stopListening(dimmensionsChangedEventSubscriptionId) - - /* icons */ - - imageStopLossPhase = undefined; - imageTakeProfitPhase = undefined; - imageRecord = undefined; - - /* Destroy References */ - - singleFile = undefined; - - datetime = undefined; - timePeriod = undefined; - - dataFile = undefined; - fileCursor = undefined; - - } catch (err) { - - if (ERROR_LOG === true) { logger.write("[ERROR] finalize -> err = " + err.stack); } - } - } - - function initialize(pStorage, pExchange, pMarket, pDatetime, pTimePeriod, callBackFunction) { - - try { - - /* Store the information received. */ - - singleFile = pStorage.singleFile; - - datetime = pDatetime; - timePeriod = pTimePeriod; - - dataFile = singleFile.getFile(timePeriod); - - /* Listen to the necesary events. */ - - zoomChangedEventSubscriptionId = viewPort.eventHandler.listenToEvent("Zoom Changed", onZoomChanged); - offsetChangedEventSubscriptionId = viewPort.eventHandler.listenToEvent("Offset Changed", onOffsetChanged); - filesUpdatedEventSubscriptionId = singleFile.eventHandler.listenToEvent("Files Updated", onFilesUpdated); - dragFinishedEventSubscriptionId = canvas.eventHandler.listenToEvent("Drag Finished", onDragFinished); - - /* Get ready for plotting. */ - - recalculate(); - - /* Ready for when dimmension changes. */ - - dimmensionsChangedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('Dimmensions Changed', function () { - recalculate(); - }) - - callBackFunction(); - - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] initialize -> err = " + err.stack); } - } - } - - function getContainer(point) { - let container; - /* First we check if this point is inside this space. */ - if (this.container.frame.isThisPointHere(point) === true) { - return this.container; - } else { - /* This point does not belong to this space. */ - return undefined; - } - } - - function onFilesUpdated() { - try { - dataFile = singleFile.getFile(timePeriod); - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] onFilesUpdated -> err = " + err.stack); } - } - } - - function setTimePeriod(pTimePeriod) { - try { - if (timePeriod !== pTimePeriod) { - timePeriod = pTimePeriod; - dataFile = singleFile.getFile(timePeriod); - recalculate(); - } - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] setTimePeriod -> err = " + err.stack); } - } - } - - function setDatetime(pDatetime) { - datetime = pDatetime; - } - - function draw() { - try { - // Nothing to draw for now - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] draw -> err = " + err.stack); } - } - } - - function recalculate() { - - try { - - records = [] - - if (timePeriod >= _1_HOUR_IN_MILISECONDS) { - - recalculateUsingMarketFiles(); - - } else { - - recalculateUsingDailyFiles(); - - } - - thisObject.container.eventHandler.raiseEvent("Records Changed", records); - - } catch (err) { - - if (ERROR_LOG === true) { logger.write("[ERROR] recalculate -> err = " + err.stack); } - } - } - - function recalculate() { - - try { - - if (dataFile === undefined) { return; } // Initialization not complete yet. - - records = []; - - for (let i = 0; i < dataFile.length; i++) { - - let record = {}; - - record.begin = dataFile[i][0]; - record.end = dataFile[i][1]; - record.type = dataFile[i][2]; - record.rate = dataFile[i][3]; - record.amount = dataFile[i][4]; - record.balanceA = dataFile[i][5]; - record.balanceB = dataFile[i][6]; - record.profit = dataFile[i][7]; - record.lastTradeProfitLoss = dataFile[i][8]; - record.stopLoss = dataFile[i][9]; - record.roundtrips = dataFile[i][10]; - record.hits = dataFile[i][11]; - record.fails = dataFile[i][12]; - record.hitRatio = dataFile[i][13]; - record.ROI = dataFile[i][14]; - record.periods = dataFile[i][15]; - record.days = dataFile[i][16]; - record.anualizedRateOfReturn = dataFile[i][17]; - record.sellRate = dataFile[i][18]; - record.lastTradeROI = dataFile[i][19]; - record.strategy = dataFile[i][20]; - record.strategyPhase = dataFile[i][21]; - record.takeProfit = dataFile[i][22]; - record.stopLossPhase = dataFile[i][23]; - record.takeProfitPhase = dataFile[i][24]; - record.positionSize = dataFile[i][26]; // 25 is the message for the executor - record.initialBalanceA = dataFile[i][27]; - record.minimunBalanceA = dataFile[i][28]; - record.maximunBalanceA = dataFile[i][29]; - record.initialBalanceB = dataFile[i][30]; - record.minimunBalanceB = dataFile[i][31]; - record.maximunBalanceB = dataFile[i][32]; - record.baseAsset = dataFile[i][33]; - record.positionPeriods = dataFile[i][34]; - record.positionDays = dataFile[i][35]; - - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentRecord); - } - - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] recalculate -> err = " + err.stack); } - } - } - - function recalculateScale() { - try { - // No scale needed - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] recalculateScale -> err = " + err.stack); } - } - } - - function plotChart() { - try { - let userPosition = getUserPosition() - let userPositionDate = userPosition.point.x - - let record; - - /* Now we calculate and plot the records */ - for (let i = 0; i < records.length; i++) { // We do not start in 0 so as to be able to read the previous record i - 1 - - record = records[i]; - - /* Send the current record to the panel */ - - if (userPositionDate >= record.begin && userPositionDate <= record.end) { - - let eventRecords = { - currentRecord: record, - lastRecord: records[records.length - 1] - }; - thisObject.container.eventHandler.raiseEvent("Current Record Changed", eventRecords); - } - } - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] plotChart -> err = " + err.stack); } - } - } - - function onZoomChanged(event) { - try { - recalculate(); - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] onZoomChanged -> err = " + err.stack); } - } - } - - function onOffsetChanged() { - try { - if (Math.random() * 100 > 95) { - recalculate() - }; - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] onOffsetChanged -> err = " + err.stack); } - } - } - - function onDragFinished() { - try { - recalculate(); - } catch (err) { - if (ERROR_LOG === true) { logger.write("[ERROR] onDragFinished -> err = " + err.stack); } - } - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/FinalResultsPanel.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/FinalResultsPanel.js deleted file mode 100644 index 0907ae991..000000000 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/FinalResultsPanel.js +++ /dev/null @@ -1,360 +0,0 @@ - -function newAAMastersPlottersTradingSimulationTradingSimulationFinalResultsPanel() { - - let thisObject = { - fitFunction: undefined, - onEventRaised: onEventRaised, - container: undefined, - draw: draw, - getContainer: getContainer, - initialize: initialize, - finalize: finalize - }; - - let container = newContainer(); - container.initialize(); - thisObject.container = container; - - container.displacement.containerName = "Final Results"; - container.frame.containerName = "Final Results"; - - let currentRecord; - let lastRecord; - let panelTabButton - - - return thisObject; - - function initialize() { - - thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL * 2; - thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL * 2.5; - - thisObject.container.frame.position.x = viewPort.visibleArea.topLeft.x + thisObject.container.frame.width * 1; - thisObject.container.frame.position.y = viewPort.visibleArea.topRight.y; - - panelTabButton = newPanelTabButton() - panelTabButton.parentContainer = thisObject.container - panelTabButton.container.frame.parentFrame = thisObject.container.frame - panelTabButton.fitFunction = thisObject.fitFunction - panelTabButton.initialize() - - } - - function finalize() { - - } - - function getContainer(point) { - - let container; - - container = panelTabButton.getContainer(point) - if (container !== undefined) { return container } - - if (thisObject.container.frame.isThisPointHere(point, true) === true) { - - let checkPoint = { - x: point.x, - y: point.y - } - - checkPoint = thisObject.fitFunction(checkPoint) - - if (point.x === checkPoint.x && point.y === checkPoint.y) { - return thisObject.container; - } - } - } - - - function onEventRaised(records) { - - currentRecord = records.currentRecord; - lastRecord = records.lastRecord - - } - - - function draw() { - - thisObject.container.frame.draw(false, false, true, thisObject.fitFunction); - - plotCurrentRecordInfo(); - - panelTabButton.draw() - } - - - function plotCurrentRecordInfo() { - - if (currentRecord === undefined) { return; } - if (lastRecord === undefined) { return; } - - const frameBodyHeight = thisObject.container.frame.getBodyHeight(); - const frameTitleHeight = thisObject.container.frame.height - frameBodyHeight; - - const X_AXIS = thisObject.container.frame.width / 2; - const Y_AXIS = frameTitleHeight + frameBodyHeight / 2; - - - /* put the labels with the records values */ - - browserCanvasContext.beginPath(); - - let y = 0; - let increment = 0.024; - - y = y + increment; - y = y + increment; - printLabel('Current Position', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1', 14); - - y = y + increment; - printLabel('Size', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.positionSize, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Rate', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.sellRate, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Stop', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.stopLoss, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Take Profit', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.takeProfit, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Periods', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((currentRecord.positionPeriods), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Days', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((currentRecord.positionDays).toFixed(2), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - y = y + increment; - printLabel('Latest Position', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1', 14); - - y = y + increment; - printLabel('Profit / Loss', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((currentRecord.lastTradeProfitLoss), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('ROI', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((currentRecord.lastTradeROI).toFixed(2) + ' % ', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - - - let bifurcationLevel = y - y = y + increment; - y = y + increment; - printLabel('Partial Results', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1', 14); - - y = y + increment; - printLabel('Profit / Loss', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.profit, X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Trades', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.roundtrips, X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Hits', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.hits, X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Fails', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.fails, X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Hit Ratio', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((currentRecord.hitRatio * 100).toFixed(2) + ' %', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('ROI', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((currentRecord.ROI * 100).toFixed(2) + ' %', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Periods', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.periods, X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Days', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(currentRecord.days, X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Anualized Rate of Return', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((currentRecord.anualizedRateOfReturn * 100).toFixed(2) + ' %', X_AXIS - thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - - y = bifurcationLevel - - y = y + increment; - y = y + increment; - printLabel('Final Results', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1', 14); - - y = y + increment; - printLabel('Profit / Loss', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(lastRecord.profit, X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Trades', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(lastRecord.roundtrips, X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Hits', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(lastRecord.hits, X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Fails', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(lastRecord.fails, X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Hit Ratio', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((lastRecord.hitRatio * 100).toFixed(2) + ' %', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('ROI', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((lastRecord.ROI * 100).toFixed(2) + ' %', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Periods', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(lastRecord.periods, X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Days', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel(lastRecord.days, X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - y = y + increment; - printLabel('Anualized Rate of Return', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '0.60'); - y = y + increment; - printLabel((lastRecord.anualizedRateOfReturn * 100).toFixed(2) + ' %', X_AXIS + thisObject.container.frame.width / 4, frameTitleHeight + frameBodyHeight * y, '1.00', 14); - - - - function printLabel(labelToPrint, x, y, opacity, fontSize) { - - let labelPoint; - if (fontSize === undefined) { fontSize = 10 }; - - browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY; - - let label = '' + labelToPrint; - if (isNaN(label) === false) { - label = Number(label).toLocaleString(); - if ((label === "-0" || label === "0") && (labelToPrint !== 0) && (labelToPrint !== "0.00")) { - label = Number(labelToPrint).toFixed(6); - } - } - let xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO; - - labelPoint = { - x: x - xOffset, - y: y - }; - - labelPoint = thisObject.container.frame.frameThisPoint(labelPoint); - labelPoint = thisObject.fitFunction(labelPoint) - - browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK + ', ' + opacity + ')'; - browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y); - - } - - browserCanvasContext.closePath(); - browserCanvasContext.fill(); - - /* Parameters */ - let params - let paramsArray = [] - let positionTaken = false - - if (currentRecord.positionSize > 0) { - positionTaken = true - } - - params = {} - params.VALUE = currentRecord.balanceA; - params.INIT_VALUE = currentRecord.initialBalanceA - params.MIN_VALUE = currentRecord.minimunBalanceA - params.MAX_VALUE = currentRecord.maximunBalanceA - params.ASSET_LABEL = 'Asset A' - params.ASSET_NAME = DEFAULT_MARKET.assetB - params.LEFT_OFFSET = 100 - params.POSITION_TAKEN = positionTaken - params.BASE_ASSET = currentRecord.baseAsset - params.DECIMALS = 4 - - paramsArray.push(params) - - params = {} - params.VALUE = currentRecord.balanceB; - params.MIN_VALUE = currentRecord.minimunBalanceB - params.INIT_VALUE = currentRecord.initialBalanceB - params.MAX_VALUE = currentRecord.maximunBalanceB - params.ASSET_LABEL = 'Asset B' - params.ASSET_NAME = DEFAULT_MARKET.assetA - params.LEFT_OFFSET = 220 - params.POSITION_TAKEN = positionTaken - params.BASE_ASSET = currentRecord.baseAsset - params.DECIMALS = 2 - - paramsArray.push(params) - - canvas.cockpitSpace.assetBalances.setParamsArray(paramsArray) - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecution.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecution.js index 9db58a633..cecae899f 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecution.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecution.js @@ -342,7 +342,7 @@ function plotChart() { try { - let currentRecord + let record let point = { x: 0, @@ -358,7 +358,7 @@ for (let i = 0; i < history.length; i++) { record = history[i]; - currentRecord = record + record = record let timestamp = record.date // Math.trunc(record.date / timeFrame) * timeFrame + timeFrame / 2; @@ -427,7 +427,7 @@ point2.y = point2.y - diff; point3.y = point3.y - diff; - currentRecord.timeFrame = diff + record.timeFrame = diff point2 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point2); point3 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point3); @@ -502,7 +502,7 @@ point2.y = point2.y + diff; point3.y = point3.y + diff; - currentRecord.timeFrame = diff + record.timeFrame = diff point2 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point2); point3 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point3); @@ -575,7 +575,7 @@ point2.y = point2.y - diff; point3.y = point3.y - diff; - currentRecord.timeFrame = diff + record.timeFrame = diff point2 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point2); point3 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point3); @@ -649,7 +649,7 @@ point2.y = point2.y + diff; point3.y = point3.y + diff; - currentRecord.timeFrame = diff + record.timeFrame = diff point2 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point2); point3 = canvas.chartingSpace.viewport.fitIntoVisibleArea(point3); @@ -683,7 +683,7 @@ /* Send the current record to the panel */ if (userPositionDate >= record.date - 30000 && userPositionDate < record.date + 30000) { - thisObject.container.eventHandler.raiseEvent("Current Record Changed", currentRecord); + thisObject.container.eventHandler.raiseEvent("Current Record Changed", record); } /* Circles */ diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecutionPanel.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecutionPanel.js index 48410e698..ad1e51b7b 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecutionPanel.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/SimulationExecutionPanel.js @@ -19,7 +19,7 @@ function newTradingEnginesPlottersTradingSimulationSimulationExecutionSimulation container.frame.containerName = "Trading Execution"; - let currentRecord; + let record; let upDownButton return thisObject; @@ -30,7 +30,7 @@ function newTradingEnginesPlottersTradingSimulationSimulationExecutionSimulation thisObject.fitFunction = undefined thisObject.isVisible = undefined - currentRecord = undefined + record = undefined upDownButton.finalize() upDownButton = undefined } @@ -75,7 +75,7 @@ function newTradingEnginesPlottersTradingSimulationSimulationExecutionSimulation function onRecordChange(lastCurrentCandle) { - currentRecord = lastCurrentCandle; + record = lastCurrentCandle; } @@ -93,7 +93,7 @@ function newTradingEnginesPlottersTradingSimulationSimulationExecutionSimulation function plot() { - if (currentRecord === undefined) { return; } + if (record === undefined) { return; } const frameBodyHeight = thisObject.container.frame.getBodyHeight(); const frameTitleHeight = thisObject.container.frame.height - frameBodyHeight; @@ -101,7 +101,7 @@ function newTradingEnginesPlottersTradingSimulationSimulationExecutionSimulation const X_AXIS = thisObject.container.frame.width / 2; const Y_AXIS = frameTitleHeight + frameBodyHeight / 2; - record = currentRecord + record = record point = { x: X_AXIS, @@ -395,28 +395,28 @@ function newTradingEnginesPlottersTradingSimulationSimulationExecutionSimulation browserCanvasContext.beginPath(); let y; - let recordDate = new Date(currentRecord.datetime) + let recordDate = new Date(record.datetime) printLabel('Market Rate', X_AXIS, frameTitleHeight + frameBodyHeight * 0.05 / 2, '1'); - printLabel(currentRecord.marketRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.10 / 2, '0.50'); + printLabel(record.marketRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.10 / 2, '0.50'); printLabel('Sell Rate', X_AXIS, frameTitleHeight + frameBodyHeight * 0.15 / 2, '1'); - printLabel(currentRecord.lastSellRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.20 / 2, '0.50'); + printLabel(record.lastSellRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.20 / 2, '0.50'); printLabel('Sell Executed Rate', X_AXIS, frameTitleHeight + frameBodyHeight * 0.25 / 2, '1'); - printLabel(currentRecord.sellExecRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.30 / 2, '0.50'); + printLabel(record.sellExecRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.30 / 2, '0.50'); printLabel('Buy Rate', X_AXIS, frameTitleHeight + frameBodyHeight * 0.35 / 2, '1'); - printLabel(currentRecord.lastBuyRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.40 / 2, '0.40'); + printLabel(record.lastBuyRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.40 / 2, '0.40'); printLabel('Buy Executed Rate', X_AXIS, frameTitleHeight + frameBodyHeight * 0.45 / 2, '1'); - printLabel(currentRecord.buyExecRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.50 / 2, '0.50'); + printLabel(record.buyExecRate, X_AXIS, frameTitleHeight + frameBodyHeight * 0.50 / 2, '0.50'); printLabel('ROI Asset A', X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.25 / 2), '1'); - printLabel((currentRecord.combinedProfitsB).toFixed(2) + '%', X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.30 / 2), '0.50'); + printLabel((record.combinedProfitsB).toFixed(2) + '%', X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.30 / 2), '0.50'); printLabel('ROI Asset B', X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.15 / 2), '1'); - printLabel((currentRecord.combinedProfitsA).toFixed(2) + '%', X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.20 / 2), '0.50'); + printLabel((record.combinedProfitsA).toFixed(2) + '%', X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.20 / 2), '0.50'); printLabel('Datetime', X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.05 / 2), '1'); printLabel(recordDate.toUTCString(), X_AXIS, frameTitleHeight + frameBodyHeight * (1 - 0.1 / 2), '0.50'); diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Strategies.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Strategies.js index 2e82c2267..34b0c28ea 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Strategies.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Strategies.js @@ -27,7 +27,7 @@ // Secondary functions and properties. - currentRecord: undefined + record: undefined }; /* this is part of the module template */ @@ -383,7 +383,7 @@ record.number = dailyFile[i][3]; record.beginRate = dailyFile[i][4]; record.endRate = dailyFile[i][5]; - record.triggerOnSituation = dailyFile[i][6]; + record.situationName = dailyFile[i][6]; record.name = dailyFile[i][7]; if ( @@ -395,8 +395,8 @@ if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Strategy Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Strategy Changed", thisObject.record); } } @@ -456,7 +456,7 @@ record.number = marketFile[i][3]; record.beginRate = marketFile[i][4]; record.endRate = marketFile[i][5]; - record.triggerOnSituation = marketFile[i][6]; + record.situationName = marketFile[i][6]; record.name = marketFile[i][7]; if (record.begin >= leftDate.valueOf() && record.end <= rightDate.valueOf()) { @@ -468,8 +468,8 @@ (record.begin >= coordinateSystem.min.x && record.end <= coordinateSystem.max.x) ) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Strategy Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Strategy Changed", thisObject.record); } } @@ -588,8 +588,8 @@ if (record.name !== "New Strategy") { printLabel(record.name, recordPoint1.x + 5, positionY - 15, '1', 12); } - if (record.triggerOnSituation !== "New Situation") { - printLabel(record.triggerOnSituation, recordPoint1.x + 5, positionY, '1', 12); + if (record.situationName !== "New Situation") { + printLabel(record.situationName, recordPoint1.x + 5, positionY, '1', 12); } } @@ -609,9 +609,9 @@ if (userPositionDate >= record.begin && userPositionDate <= record.end) { - let currentRecord = { + let record = { }; - thisObject.container.eventHandler.raiseEvent("Current Strategy Record Changed", currentRecord); + thisObject.container.eventHandler.raiseEvent("Current Strategy Record Changed", record); } function drawStick(point1, point2) { diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Trades.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Trades.js index 9da7b37b6..cad27fe7c 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Trades.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/Trades.js @@ -27,7 +27,7 @@ // Secondary functions and properties. - currentRecord: undefined + record: undefined }; /* this is part of the module template */ @@ -366,11 +366,11 @@ record.begin = dailyFile[i][0]; record.end = dailyFile[i][1]; record.status = dailyFile[i][2]; - record.lastTradeROI = dailyFile[i][3]; + record.variable_last_position_ROI = dailyFile[i][3]; record.beginRate = dailyFile[i][4]; record.endRate = dailyFile[i][5]; record.exitType = dailyFile[i][6]; - record.takePositionSituation = dailyFile[i][7]; + record.situationName = dailyFile[i][7]; if ( (record.begin >= farLeftDate.valueOf() && record.end <= farRightDate.valueOf()) && @@ -381,8 +381,8 @@ if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Trade Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Trade Changed", thisObject.record); } } @@ -432,11 +432,11 @@ record.begin = marketFile[i][0]; record.end = marketFile[i][1]; record.status = marketFile[i][2]; - record.lastTradeROI = marketFile[i][3]; + record.variable_last_position_ROI = marketFile[i][3]; record.beginRate = marketFile[i][4]; record.endRate = marketFile[i][5]; record.exitType = marketFile[i][6]; - record.takePositionSituation = marketFile[i][7]; + record.situationName = marketFile[i][7]; if ( (record.begin >= leftDate.valueOf() && record.end <= rightDate.valueOf()) && @@ -447,8 +447,8 @@ if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Trade Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Trade Changed", thisObject.record); } } @@ -478,9 +478,9 @@ if (userPositionDate >= record.begin && userPositionDate <= record.end) { - let currentRecord = { + let record = { }; - thisObject.container.eventHandler.raiseEvent("Current Trade Record Changed", currentRecord); + thisObject.container.eventHandler.raiseEvent("Current Trade Record Changed", record); } let recordPoint1 = { @@ -518,7 +518,7 @@ recordPoint2 = thisObject.fitFunction(recordPoint2); recordPoint3 = thisObject.fitFunction(recordPoint3); - let line0 = record.takePositionSituation + let line0 = record.situationName let line1 = ''; let line2 = ''; @@ -541,10 +541,10 @@ line1 = 'Open Position'; } - if (record.lastTradeROI < 0) { - line2 = 'ROI:' + (record.lastTradeROI).toFixed(2) + ' %'; + if (record.variable_last_position_ROI < 0) { + line2 = 'ROI:' + (record.variable_last_position_ROI).toFixed(2) + ' %'; } else { - line2 = 'ROI:' + (record.lastTradeROI).toFixed(2) + ' %'; + line2 = 'ROI:' + (record.variable_last_position_ROI).toFixed(2) + ' %'; } /* Draw the triangle that represents the trade. */ @@ -559,7 +559,7 @@ let opacity = '0.25'; - if (record.lastTradeROI > 0) { + if (record.variable_last_position_ROI > 0) { browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', ' + opacity + ')'; browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.GREEN + ', ' + opacity + ')'; } else { diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulation.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulation.js index f9b3b5787..52b8d4704 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulation.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulation.js @@ -26,7 +26,7 @@ // Secondary functions and properties. - currentRecord: undefined + record: undefined }; /* this is part of the module template */ @@ -149,7 +149,7 @@ /* Loading a few icons */ - imageStopLossPhase = canvas.designSpace.iconByUiObjectType.get('Stop'); + imageStopLossPhase = canvas.designSpace.iconByUiObjectType.get('Managed Stop Loss'); imageTakeProfitPhase = canvas.designSpace.iconByUiObjectType.get('Phase'); imageRecord = canvas.designSpace.iconByUiObjectType.get('Trading System'); @@ -353,42 +353,38 @@ record.begin = dailyFile[i][0]; record.end = dailyFile[i][1]; - record.type = dailyFile[i][2]; - record.rate = dailyFile[i][3]; - record.amount = dailyFile[i][4]; - record.balanceA = dailyFile[i][5]; - record.balanceB = dailyFile[i][6]; - record.profit = dailyFile[i][7]; - record.lastTradeProfitLoss = dailyFile[i][8]; - record.stopLoss = dailyFile[i][9]; - record.roundtrips = dailyFile[i][10]; - record.hits = dailyFile[i][11]; - record.fails = dailyFile[i][12]; - record.hitRatio = dailyFile[i][13]; - record.ROI = dailyFile[i][14]; - record.periods = dailyFile[i][15]; - record.days = dailyFile[i][16]; - record.anualizedRateOfReturn = dailyFile[i][17]; - record.sellRate = dailyFile[i][18]; - record.lastTradeROI = dailyFile[i][19]; - record.strategy = dailyFile[i][20]; - record.strategyPhase = dailyFile[i][21]; - record.takeProfit = dailyFile[i][22]; - record.stopLossPhase = dailyFile[i][23]; - record.takeProfitPhase = dailyFile[i][24]; - record.positionSize = dailyFile[i][26]; // 25 is the message for the executor - record.initialBalanceA = dailyFile[i][27]; - record.minimunBalanceA = dailyFile[i][28]; - record.maximunBalanceA = dailyFile[i][29]; - record.initialBalanceB = dailyFile[i][30]; - record.minimunBalanceB = dailyFile[i][31]; - record.maximunBalanceB = dailyFile[i][32]; - record.baseAsset = dailyFile[i][33]; - record.quotedAsset = dailyFile[i][34]; - record.marketBaseAsset = dailyFile[i][35]; - record.marketQuotedAsset = dailyFile[i][36]; - record.positionPeriods = dailyFile[i][37]; - record.positionDays = dailyFile[i][38]; + record.variable_current_balance_baseAsset = dailyFile[i][2]; + record.variable_current_balance_quotedAsset = dailyFile[i][3]; + record.variable_episode_stat_profitLoss = dailyFile[i][4]; + record.variable_last_position_profitLoss = dailyFile[i][5]; + record.variable_current_position_stopLoss_value = dailyFile[i][6]; + record.variable_episode_count_positions = dailyFile[i][7]; + record.variable_episode_count_hits = dailyFile[i][8]; + record.variable_episode_count_fails = dailyFile[i][9]; + record.variable_episode_stat_hitRatio = dailyFile[i][10]; + record.variable_episode_stat_ROI = dailyFile[i][11]; + record.variable_episode_count_periods = dailyFile[i][12]; + record.variable_episode_stat_days = dailyFile[i][13]; + record.variable_episode_stat_anualizedRateOfReturn = dailyFile[i][14]; + record.variable_current_position_rate = dailyFile[i][15]; + record.variable_last_position_ROI = dailyFile[i][16]; + record.strategy = dailyFile[i][17]; + record.variable_current_position_takeProfit_value = dailyFile[i][18]; + record.variable_current_position_stopLoss_phase = dailyFile[i][19]; + record.variable_current_position_takeProfit_phase = dailyFile[i][20]; + record.variable_current_position_size = dailyFile[i][21]; // 25 is the message for the executor + record.variable_episode_parameters_initial_balance_baseAsset = dailyFile[i][22]; + record.variable_episode_parameters_minimum_balance_baseAsset = dailyFile[i][23]; + record.variable_episode_parameters_maximum_balance_baseAsset = dailyFile[i][24]; + record.variable_episode_parameters_initial_balance_quotedAsset = dailyFile[i][25]; + record.variable_episode_parameters_minimum_balance_quotedAsset = dailyFile[i][26]; + record.variable_episode_parameters_maximum_balance_quotedAsset = dailyFile[i][27]; + record.variable_episode_parameters_baseAsset = dailyFile[i][28]; + record.variable_episode_parameters_quotedAsset = dailyFile[i][29]; + record.variable_episode_parameters_marketBaseAsset = dailyFile[i][30]; + record.variable_episode_parameters_marketQuotedAsset = dailyFile[i][31]; + record.variable_current_position_count_periods = dailyFile[i][32]; + record.variable_current_position_stat_days = dailyFile[i][33]; if ( (record.begin >= farLeftDate.valueOf() && record.end <= farRightDate.valueOf()) && @@ -399,8 +395,8 @@ if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.record); } } @@ -447,42 +443,37 @@ record.begin = marketFile[i][0]; record.end = marketFile[i][1]; - record.type = marketFile[i][2]; - record.rate = marketFile[i][3]; - record.amount = marketFile[i][4]; - record.balanceA = marketFile[i][5]; - record.balanceB = marketFile[i][6]; - record.profit = marketFile[i][7]; - record.lastTradeProfitLoss = marketFile[i][8]; - record.stopLoss = marketFile[i][9]; - record.roundtrips = marketFile[i][10]; - record.hits = marketFile[i][11]; - record.fails = marketFile[i][12]; - record.hitRatio = marketFile[i][13]; - record.ROI = marketFile[i][14]; - record.periods = marketFile[i][15]; - record.days = marketFile[i][16]; - record.anualizedRateOfReturn = marketFile[i][17]; - record.sellRate = marketFile[i][18]; - record.lastTradeROI = marketFile[i][19]; - record.strategy = marketFile[i][20]; - record.strategyPhase = marketFile[i][21]; - record.takeProfit = marketFile[i][22]; - record.stopLossPhase = marketFile[i][23]; - record.takeProfitPhase = marketFile[i][24]; - record.positionSize = marketFile[i][26]; // 25 is the message for the executor - record.initialBalanceA = marketFile[i][27]; - record.minimunBalanceA = marketFile[i][28]; - record.maximunBalanceA = marketFile[i][29]; - record.initialBalanceB = marketFile[i][30]; - record.minimunBalanceB = marketFile[i][31]; - record.maximunBalanceB = marketFile[i][32]; - record.baseAsset = marketFile[i][33]; - record.quotedAsset = marketFile[i][34]; - record.marketBaseAsset = marketFile[i][35]; - record.marketQuotedAsset = marketFile[i][36]; - record.positionPeriods = marketFile[i][37]; - record.positionDays = marketFile[i][38]; + record.variable_current_balance_baseAsset = marketFile[i][2]; + record.variable_current_balance_quotedAsset = marketFile[i][3]; + record.variable_episode_stat_profitLoss = marketFile[i][4]; + record.variable_last_position_profitLoss = marketFile[i][5]; + record.variable_current_position_stopLoss_value = marketFile[i][6]; + record.variable_episode_count_positions = marketFile[i][7]; + record.variable_episode_count_hits = marketFile[i][8]; + record.variable_episode_count_fails = marketFile[i][9]; + record.variable_episode_stat_hitRatio = marketFile[i][10]; + record.variable_episode_stat_ROI = marketFile[i][11]; + record.variable_episode_count_periods = marketFile[i][12]; + record.variable_episode_stat_days = marketFile[i][13]; + record.variable_episode_stat_anualizedRateOfReturn = marketFile[i][14]; + record.variable_current_position_rate = marketFile[i][15]; + record.variable_last_position_ROI = marketFile[i][16]; + record.variable_current_position_takeProfit_value = marketFile[i][17]; + record.variable_current_position_stopLoss_phase = marketFile[i][18]; + record.variable_current_position_takeProfit_phase = marketFile[i][19]; + record.variable_current_position_size = marketFile[i][20]; + record.variable_episode_parameters_initial_balance_baseAsset = marketFile[i][21]; + record.variable_episode_parameters_minimum_balance_baseAsset = marketFile[i][22]; + record.variable_episode_parameters_maximum_balance_baseAsset = marketFile[i][23]; + record.variable_episode_parameters_initial_balance_quotedAsset = marketFile[i][24]; + record.variable_episode_parameters_minimum_balance_quotedAsset = marketFile[i][25]; + record.variable_episode_parameters_maximum_balance_quotedAsset = marketFile[i][26]; + record.variable_episode_parameters_baseAsset = marketFile[i][27]; + record.variable_episode_parameters_quotedAsset = marketFile[i][28]; + record.variable_episode_parameters_marketBaseAsset = marketFile[i][29]; + record.variable_episode_parameters_marketQuotedAsset = marketFile[i][30]; + record.variable_current_position_count_periods = marketFile[i][31]; + record.variable_current_position_stat_days = marketFile[i][32]; if ( (record.begin >= leftDate.valueOf() && record.end <= rightDate.valueOf()) && @@ -493,8 +484,8 @@ if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) { - thisObject.currentRecord = record; - thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentRecord); + thisObject.record = record; + thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.record); } } @@ -521,11 +512,11 @@ record = records[i]; /* Contributing to Auto-Scale*/ - if (record.stopLoss !== 0) { - coordinateSystem.reportYValue(record.stopLoss) + if (record.variable_current_position_stopLoss_value !== 0) { + coordinateSystem.reportYValue(record.variable_current_position_stopLoss_value) } - if (record.takeProfit !== 0) { - coordinateSystem.reportYValue(record.takeProfit) + if (record.variable_current_position_takeProfit_value !== 0) { + coordinateSystem.reportYValue(record.variable_current_position_takeProfit_value) } /* Send the current record to the panel */ @@ -533,7 +524,7 @@ if (userPositionDate >= record.begin && userPositionDate <= record.end) { let eventRecords = { - currentRecord: record + record: record }; thisObject.container.eventHandler.raiseEvent("Current Record Changed", eventRecords); } @@ -542,12 +533,12 @@ let takeProfitPhase = -1; if (i > 0) { - if (record.stopLossPhase !== records[i - 1].stopLossPhase) { - stopLossPhase = record.stopLossPhase; + if (record.variable_current_position_stopLoss_phase !== records[i - 1].variable_current_position_stopLoss_phase) { + stopLossPhase = record.variable_current_position_stopLoss_phase; } - if (record.takeProfitPhase !== records[i - 1].takeProfitPhase) { - takeProfitPhase = record.takeProfitPhase; + if (record.variable_current_position_takeProfit_phase !== records[i - 1].variable_current_position_takeProfit_phase) { + takeProfitPhase = record.variable_current_position_takeProfit_phase; } } @@ -573,15 +564,15 @@ let recordPoint5 = { x: record.begin, - y: record.stopLoss + y: record.variable_current_position_stopLoss_value }; let recordPoint6 = { x: record.end, - y: record.stopLoss + y: record.variable_current_position_stopLoss_value }; - if (record.stopLoss === 0) { // Put these points out of range if stopLoss is zero. + if (record.variable_current_position_stopLoss_value === 0) { // Put these points out of range if stopLoss is zero. recordPoint5.x = 0; recordPoint6.x = 0; @@ -589,15 +580,15 @@ let recordPoint7 = { x: record.begin, - y: record.sellRate + y: record.variable_current_position_rate }; let recordPoint8 = { x: record.end, - y: record.sellRate + y: record.variable_current_position_rate }; - if (record.sellRate === 0) { // Put these points out of range if sellRate is zero. + if (record.variable_current_position_rate === 0) { // Put these points out of range if sellRate is zero. recordPoint7.x = 0; recordPoint8.x = 0; @@ -605,15 +596,15 @@ let recordPoint9 = { x: record.begin, - y: record.takeProfit + y: record.variable_current_position_takeProfit_value }; let recordPoint10 = { x: record.end, - y: record.takeProfit + y: record.variable_current_position_takeProfit_value }; - if (record.takeProfit === 0) { // Put these points out of range if takeProfit is zero. + if (record.variable_current_position_takeProfit_value === 0) { // Put these points out of range if takeProfit is zero. recordPoint9.x = 0; recordPoint10.x = 0; @@ -670,7 +661,7 @@ y: -30 } let imageSize = (recordPoint2.x - recordPoint1.x) / 2; - let recordLabel = record.periods.toLocaleString() + let recordLabel = record.variable_episode_count_periods.toLocaleString() if (imageSize < 8) { if (recordLabel[recordLabel.length - 1] !== '0' || imageSize < 4) recordLabel = '' @@ -728,10 +719,10 @@ imageSize = 20; let imageToDraw; - /* Depending on what is on top, it is also the text and image of the take profit / stop loss */ + /* Depending on what is on top, it is also the text and image of the take accumulatedProfitLoss / stop loss */ let yOffset = 0 - if (record.takeProfit > record.stopLoss) { yOffset = imageSize * 2 } + if (record.variable_current_position_takeProfit_value > record.variable_current_position_stopLoss_value) { yOffset = imageSize * 2 } /* Next we are drawing the stopLoss floor / ceilling */ diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationAssetBalancesPanel.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationAssetBalancesPanel.js index 7272577da..6ff7fc997 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationAssetBalancesPanel.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationAssetBalancesPanel.js @@ -19,7 +19,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul container.frame.containerName = "Simulation Asset Balances Panel"; - let currentRecord; + let record; let lastRecord; let upDownButton @@ -56,7 +56,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul thisObject.onRecordChange = undefined thisObject.isVisible = undefined - currentRecord = undefined + record = undefined lastRecord = undefined upDownButton = undefined @@ -89,7 +89,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul function onRecordChange(records) { - currentRecord = records.currentRecord; + record = records.record; lastRecord = records.lastRecord } @@ -117,7 +117,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul function plotCurrentRecordInfo() { - if (currentRecord === undefined) { return; } + if (record === undefined) { return; } const frameBodyHeight = thisObject.container.frame.getBodyHeight(); const frameTitleHeight = thisObject.container.frame.height - frameBodyHeight; @@ -139,7 +139,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul let paramsArray = [] let positionTaken = false - if (currentRecord.positionSize > 0) { + if (record.variable_current_position_size > 0) { positionTaken = true } @@ -154,59 +154,59 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul return } - if (currentRecord.baseAsset === currentRecord.marketBaseAsset) { + if (record.variable_episode_parameters_baseAsset === record.variable_episode_parameters_marketBaseAsset) { params = {} - params.VALUE = currentRecord.balanceA; - params.INIT_VALUE = currentRecord.initialBalanceA - params.MIN_VALUE = currentRecord.minimunBalanceA - params.MAX_VALUE = currentRecord.maximunBalanceA + params.VALUE = record.variable_current_balance_baseAsset; + params.INIT_VALUE = record.variable_episode_parameters_initial_balance_baseAsset + params.MIN_VALUE = record.variable_episode_parameters_minimum_balance_baseAsset + params.MAX_VALUE = record.variable_episode_parameters_maximum_balance_baseAsset params.ASSET_LABEL = 'Base' - params.ASSET_NAME = currentRecord.baseAsset + params.ASSET_NAME = record.variable_episode_parameters_baseAsset params.LEFT_OFFSET = X_AXIS params.POSITION_TAKEN = positionTaken - params.BASE_ASSET = currentRecord.baseAsset + params.BASE_ASSET = record.variable_episode_parameters_baseAsset params.DECIMALS = 4 paramsArray.push(params) params = {} - params.VALUE = currentRecord.balanceB; - params.MIN_VALUE = currentRecord.minimunBalanceB - params.INIT_VALUE = currentRecord.initialBalanceB - params.MAX_VALUE = currentRecord.maximunBalanceB + params.VALUE = record.variable_current_balance_quotedAsset; + params.MIN_VALUE = record.variable_episode_parameters_minimum_balance_quotedAsset + params.INIT_VALUE = record.variable_episode_parameters_initial_balance_quotedAsset + params.MAX_VALUE = record.variable_episode_parameters_maximum_balance_quotedAsset params.ASSET_LABEL = 'Quoted' - params.ASSET_NAME = currentRecord.quotedAsset + params.ASSET_NAME = record.variable_episode_parameters_quotedAsset params.LEFT_OFFSET = X_AXIS params.POSITION_TAKEN = positionTaken - params.BASE_ASSET = currentRecord.baseAsset + params.BASE_ASSET = record.variable_episode_parameters_baseAsset params.DECIMALS = 2 paramsArray.push(params) } else { params = {} - params.VALUE = currentRecord.balanceB; - params.INIT_VALUE = currentRecord.initialBalanceB - params.MIN_VALUE = currentRecord.minimunBalanceB - params.MAX_VALUE = currentRecord.maximunBalanceB + params.VALUE = record.variable_current_balance_quotedAsset; + params.INIT_VALUE = record.variable_episode_parameters_initial_balance_quotedAsset + params.MIN_VALUE = record.variable_episode_parameters_minimum_balance_quotedAsset + params.MAX_VALUE = record.variable_episode_parameters_maximum_balance_quotedAsset params.ASSET_LABEL = 'Base' - params.ASSET_NAME = currentRecord.baseAsset + params.ASSET_NAME = record.variable_episode_parameters_baseAsset params.LEFT_OFFSET = X_AXIS params.POSITION_TAKEN = positionTaken - params.BASE_ASSET = currentRecord.baseAsset + params.BASE_ASSET = record.variable_episode_parameters_baseAsset params.DECIMALS = 4 paramsArray.push(params) params = {} - params.VALUE = currentRecord.balanceA; - params.MIN_VALUE = currentRecord.minimunBalanceA - params.INIT_VALUE = currentRecord.initialBalanceA - params.MAX_VALUE = currentRecord.maximunBalanceA + params.VALUE = record.variable_current_balance_baseAsset; + params.MIN_VALUE = record.variable_episode_parameters_minimum_balance_baseAsset + params.INIT_VALUE = record.variable_episode_parameters_initial_balance_baseAsset + params.MAX_VALUE = record.variable_episode_parameters_maximum_balance_baseAsset params.ASSET_LABEL = 'Quoted' - params.ASSET_NAME = currentRecord.quotedAsset + params.ASSET_NAME = record.variable_episode_parameters_quotedAsset params.LEFT_OFFSET = X_AXIS params.POSITION_TAKEN = positionTaken - params.BASE_ASSET = currentRecord.baseAsset + params.BASE_ASSET = record.variable_episode_parameters_baseAsset params.DECIMALS = 2 paramsArray.push(params) diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationCurrentPositionPanel.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationCurrentPositionPanel.js index 6fc1ab6f5..d3f7c9fbc 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationCurrentPositionPanel.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationCurrentPositionPanel.js @@ -21,7 +21,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul container.frame.containerName = "Simulation Current Position Panel"; - let currentRecord; + let record; let lastRecord; let upDownButton @@ -54,7 +54,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul thisObject.onRecordChange = undefined thisObject.isVisible = undefined - currentRecord = undefined + record = undefined lastRecord = undefined upDownButton = undefined } @@ -84,7 +84,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul function onRecordChange(records) { - currentRecord = records.currentRecord; + record = records.record; lastRecord = records.lastRecord } @@ -112,7 +112,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul function plotCurrentRecordInfo() { - if (currentRecord === undefined) { return; } + if (record === undefined) { return; } const frameBodyHeight = thisObject.container.frame.getBodyHeight(); const frameTitleHeight = thisObject.container.frame.height - frameBodyHeight; @@ -133,32 +133,32 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul y = y + increment; printLabel('Size', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.positionSize, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_current_position_size, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Rate', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.sellRate, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_current_position_rate, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Stop', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.stopLoss, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_current_position_stopLoss_value, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Take Profit', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.takeProfit, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_current_position_takeProfit_value, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Periods', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel((currentRecord.positionPeriods), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel((record.variable_current_position_count_periods), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Days', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel((currentRecord.positionDays).toFixed(2), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel((record.variable_current_position_stat_days).toFixed(2), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; y = y + increment; @@ -167,12 +167,12 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul y = y + increment; printLabel('Profit / Loss', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel((currentRecord.lastTradeProfitLoss), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel((record.variable_last_position_profitLoss), X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('ROI', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel((currentRecord.lastTradeROI).toFixed(2) + ' % ', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel((record.variable_last_position_ROI).toFixed(2) + ' % ', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); } } diff --git a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationPartialResultsPanel.js b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationEpisodePanel.js similarity index 72% rename from Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationPartialResultsPanel.js rename to Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationEpisodePanel.js index bd07abf7d..e1530d7cf 100644 --- a/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationPartialResultsPanel.js +++ b/Legacy-Code/TradingEngines/plotters/Plotters-Trading-Simulation/TradingSimulationEpisodePanel.js @@ -1,5 +1,5 @@  -function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimulationPartialResultsPanel() { +function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimulationEpisodePanel() { let thisObject = { fitFunction: undefined, @@ -17,9 +17,9 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul container.initialize(); thisObject.container = container; - container.frame.containerName = "Simulation Partial Results Panel"; + container.frame.containerName = "Simulation Episode Panel"; - let currentRecord; + let record; let lastRecord; let upDownButton @@ -52,7 +52,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul thisObject.onRecordChange = undefined thisObject.isVisible = undefined - currentRecord = undefined + record = undefined lastRecord = undefined upDownButton = undefined } @@ -82,7 +82,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul function onRecordChange(records) { - currentRecord = records.currentRecord; + record = records.record; lastRecord = records.lastRecord } @@ -110,7 +110,7 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul function plotCurrentRecordInfo() { - if (currentRecord === undefined) { return; } + if (record === undefined) { return; } const frameBodyHeight = thisObject.container.frame.getBodyHeight(); const frameTitleHeight = thisObject.container.frame.height - frameBodyHeight; @@ -126,52 +126,52 @@ function newTradingEnginesPlottersTradingSimulationTradingSimulationTradingSimul y = y + increment; y = y + increment; - printLabel('Partial Results', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel('Episode', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Profit / Loss', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.profit, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_episode_stat_profitLoss, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Trades', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.roundtrips, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_episode_count_positions, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Hits', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.hits, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_episode_count_hits, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Fails', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.fails, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_episode_count_fails, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Hit Ratio', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel((currentRecord.hitRatio * 100).toFixed(2) + ' %', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel((record.variable_episode_stat_hitRatio * 100).toFixed(2) + ' %', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('ROI', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel((currentRecord.ROI * 100).toFixed(2) + ' %', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel((record.variable_episode_stat_ROI * 100).toFixed(2) + ' %', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Periods', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.periods, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_episode_count_periods, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Days', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel(currentRecord.days, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel(record.variable_episode_stat_days, X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; printLabel('Anualized Rate of Return', X_AXIS, frameTitleHeight + frameBodyHeight * y, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction); y = y + increment; - printLabel((currentRecord.anualizedRateOfReturn * 100).toFixed(2) + ' %', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); + printLabel((record.variable_episode_stat_anualizedRateOfReturn * 100).toFixed(2) + ' %', X_AXIS, frameTitleHeight + frameBodyHeight * y, '1.00', 14, undefined, true, thisObject.container, thisObject.fitFunction); } } diff --git a/Super-Scripts/Masters.json b/Super-Scripts/Masters.json index 68d89e41b..ba3901034 100644 --- a/Super-Scripts/Masters.json +++ b/Super-Scripts/Masters.json @@ -1 +1 @@ -{"type":"Super Scripts","name":"Masters","code":"{ \n\"codeName\": \"Masters\"\n}","scriptsLibrary":{"type":"Scripts Library","name":"New Scripts Library","masterScripts":[{"type":"Master Script","name":"Setup Market","templateScripts":[{"type":"Template Script","name":"Install Single Market Data","templateStructure":{"type":"Template Structure","name":"New Template Structure","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Candles","id":"24c5f795-acf3-43c3-99ee-ddb521b082f4","savedPayload":{"position":{"x":39655.39345377463,"y":28084.121285348076},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles","id":"fe6065ec-4d2d-47a0-bae0-8647f0e195d7"}}},{"type":"Data Product","name":"Volumes","id":"204ec959-0861-486a-aa2a-276dbf48f775","savedPayload":{"position":{"x":39619.08803001109,"y":27971.34006849996},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Volumes","id":"1a366aef-efef-470e-88e0-2a6fbe4dd91f"}}},{"type":"Data Product","name":"Candles Channels","id":"90ee9733-ab7e-41a0-ac78-4a7661a27017","savedPayload":{"position":{"x":39594.11393185164,"y":27855.840877332517},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles Channels","id":"8c81d378-0947-4e9d-be25-58e69120152e"}}},{"type":"Data Product","name":"Candles Channels Objects","id":"9ade8e93-9071-422b-8cce-7a50d6dad739","savedPayload":{"position":{"x":39580.59887306321,"y":27738.507421368748},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles Channels Objects","id":"ba5dbb67-2030-42a0-9ff8-1ee80874f047"}}},{"type":"Data Product","name":"Bollinger Bands","id":"b5ef2cf7-3d35-45ec-848c-31970b59c04e","savedPayload":{"position":{"x":39578.655464896365,"y":27620.63069875915},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Bands","id":"29088877-8d63-4217-8d34-8742afc262f4"}}},{"type":"Data Product","name":"Percentage Bandwidth","id":"1c3881ca-24b3-4cc9-b16b-9db7aa2b4c24","savedPayload":{"position":{"x":39588.297198778826,"y":27502.946480135615},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Percentage Bandwidth","id":"3b7885a0-cd72-4a3f-a38b-3dc8c47bd068"}}},{"type":"Data Product","name":"Bollinger Channels Objects","id":"92eec33b-86be-4b73-899f-e9550f7b0df3","savedPayload":{"position":{"x":39609.428887621216,"y":27386.77785099828},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Channels Objects","id":"1f111f72-624f-4166-9f99-ab8d785d5267"}}},{"type":"Data Product","name":"Bollinger Sub-Channels Objects","id":"4b69df71-9091-4d22-9b0d-571304297033","savedPayload":{"position":{"x":39641.84608404141,"y":27273.24280350331},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Sub-Channels Objects","id":"080c87fc-4546-4482-ab11-180a33f6d9ab"}}},{"type":"Data Product","name":"Popular SMAs","id":"8a5f7e27-9ca5-4d6e-b6b9-2d6cbdda8040","savedPayload":{"position":{"x":39685.2360649887,"y":27163.433854438405},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Popular SMAs","id":"4029d5bf-a8ad-4d80-b874-2af506e0ab34"}}},{"type":"Data Product","name":"Base7 SMAs","id":"ccd9c3fd-3382-4fea-8798-a29b04efc09e","savedPayload":{"position":{"x":39739.18066448831,"y":27058.407968059906},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base7 SMAs","id":"a03ef0ea-5be9-4d40-952e-24789e77dbb6"}}},{"type":"Data Product","name":"Base11 SMAs","id":"aebc2010-a6e2-436d-8cc6-96eb0689330d","savedPayload":{"position":{"x":39803.16019594351,"y":26959.1762369918},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base11 SMAs","id":"05fabbf9-7798-4a53-995e-53ae74bef3de"}}},{"type":"Data Product","name":"Popular EMAs","id":"0fd4ef59-77b4-48cc-b00e-6a529a05cb0c","savedPayload":{"position":{"x":39876.55840037216,"y":26866.694054096202},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Popular EMAs","id":"d5894707-b065-429b-ba6b-78c71e6529fe"}}},{"type":"Data Product","name":"Base7 EMAs","id":"802917a7-816b-4e4c-bbb6-a0eaf9eb7429","savedPayload":{"position":{"x":39958.66834477317,"y":26781.851861425766},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base7 EMAs","id":"b42348a4-6a26-4d46-aa2e-cbf5fa0a4984"}}},{"type":"Data Product","name":"Base11 EMAs","id":"9109bfcb-70e7-4cf8-9047-da9567668082","savedPayload":{"position":{"x":40048.69920292061,"y":26705.466552453436},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base11 EMAs","id":"21df4f44-96eb-453b-8652-4ce2dc03424c"}}},{"type":"Data Product","name":"MACD 122609","id":"519a89d4-75bb-4c33-a63d-d65c9defbd12","savedPayload":{"position":{"x":40145.78385240617,"y":26638.273605375165},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 122609","id":"6c0c9bf0-90af-4497-b4fb-49b5872b1119"}}},{"type":"Data Product","name":"RSI14","id":"6ae3a0ab-86cf-4e81-b415-5775dfcb1d9c","savedPayload":{"position":{"x":40248.98721974834,"y":26580.920017944973},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"RSI14","id":"e24248b9-90df-4b26-b5cc-0bac80efa0c7"}}},{"type":"Data Product","name":"Base6 SMAs","id":"f4d7169e-8c26-48ae-967d-529a8ecab1f1","savedPayload":{"position":{"x":40357.31529765827,"y":26533.95809992029},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base6 SMAs","id":"4ae22e54-cd02-4e2e-b638-1c5ea9a171ac"}}},{"type":"Data Product","name":"Base5 SMAs","id":"bae79086-c788-4b79-a9af-952326480b34","savedPayload":{"position":{"x":40469.72474382882,"y":26497.840166222424},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base5 SMAs","id":"2dcb55de-1673-409e-aa52-f51e7c7f8fa3"}}},{"type":"Data Product","name":"Bollinger Channels","id":"b4847c45-613e-4862-98d3-34b28fa70fd7","savedPayload":{"position":{"x":40585.132955318084,"y":26472.914171771416},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Channels","id":"94bc3958-cc85-43a1-a178-103e84f42910"}}},{"type":"Data Product","name":"Bollinger Sub-Channels","id":"05766faf-47e0-493a-9459-a470a6691138","savedPayload":{"position":{"x":40702.42850753783,"y":26459.42033593334},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Sub-Channels","id":"79ce9c05-8a4c-4b58-850f-288037acc702"}}},{"type":"Data Product","name":"MACD 122609","id":"c5d7a5f6-2924-46a6-8d04-1100a5088da9","savedPayload":{"position":{"x":40820.28557415008,"y":26457.488926076465},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 122609","id":"6c0c9bf0-90af-4497-b4fb-49b5872b1119"}}},{"type":"Data Product","name":"MACD 031016","id":"db37547e-f473-4dca-9bae-60fb37c724a5","savedPayload":{"position":{"x":40937.95845139182,"y":26467.13880986452},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 031016","id":"d1482dc0-e9f1-47ba-b75e-961fa914a53a"}}},{"type":"Data Product","name":"MACD 081709","id":"0ec41f8a-6170-439b-9513-7c4b2a835c6d","savedPayload":{"position":{"x":41054.120384899,"y":26488.277064496815},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 081709","id":"683e6b29-9f82-4a8e-93eb-20ac0e81a750"}}},{"type":"Data Product","name":"MACD 245209","id":"03d654c7-5641-4408-bf15-54ee7203d80b","savedPayload":{"position":{"x":41167.65178678811,"y":26520.700545782674},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 245209","id":"47984765-8180-45cf-a9eb-f0a8b22d165d"}}},{"type":"Data Product","name":"Candles","id":"7b7c2f35-514c-4ad4-bdc5-7cb462020ac1","savedPayload":{"position":{"x":41277.4594798642,"y":26564.097566374792},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles","id":"2443883b-6959-42a3-ae3b-ef3102ffcdcc"}}},{"type":"Data Product","name":"Volumes","id":"4b1fcdaf-3bf1-4bdb-973d-42facd0b7104","savedPayload":{"position":{"x":41382.48641024106,"y":26618.051155654128},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Volumes","id":"3a43c6f3-bac0-404e-865f-822eb48c8660"}}},{"type":"Data Product","name":"Bollinger Channels","id":"6f3bbf18-00d5-4f5a-9c48-a7e5e73e72e2","savedPayload":{"position":{"x":41481.72199276818,"y":26682.043494609432},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Channels","id":"94bc3958-cc85-43a1-a178-103e84f42910"}}},{"type":"Data Product","name":"Bollinger Sub-Channels","id":"bbe5af35-1f9e-4a31-9b5a-47ee5999e97c","savedPayload":{"position":{"x":41574.21247985582,"y":26755.461785010735},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Sub-Channels","id":"79ce9c05-8a4c-4b58-850f-288037acc702"}}},{"type":"Data Product","name":"External Signals","id":"4e208ac6-227a-4c6d-b548-df9a06a96f2a","savedPayload":{"position":{"x":41659.072595416554,"y":26837.602086697607},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"External Signals","id":"29196db8-4c41-47ea-be7e-952cd9be9a0b"}}},{"type":"Data Product","name":"Resistances","id":"cb998af9-8b76-4163-ba59-a354e85e625b","savedPayload":{"position":{"x":41734.78246123321,"y":26926.742016325188},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Resistances","id":"7a734501-a0fb-4305-803e-7e95d477c513"}}},{"type":"Data Product","name":"Supports","id":"935f01dc-bbe2-4931-a0d9-19f820cf04c1","savedPayload":{"position":{"x":41803.59685318991,"y":27026.194745818015},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Supports","id":"bc3c43de-6312-47d4-ae3d-0c95c1b039e0"}}}],"id":"3c9e658e-9350-4b43-b95c-1dc9fbb7de7b","savedPayload":{"position":{"x":40780.522571395006,"y":27659.757222847853},"targetPosition":{"x":41312.42037870206,"y":28738.475444229476},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":5,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"922126c5-3b95-4c76-90ce-c85435c49624","savedPayload":{"position":{"x":41312.452880728124,"y":28738.45103113137},"targetPosition":{"x":41578.71473951283,"y":29277.75973906997},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"79c82a1e-4c4e-4622-b762-4ccbfdac46b7","savedPayload":{"position":{"x":41423.01737185227,"y":28696.77991254939},"targetPosition":{"x":41578.71473951283,"y":29277.75973906997},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Independent Data","name":"New Session Independent Data","id":"bf04ccea-266d-4115-b50e-fefdf29b873a"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.singleMarketData === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Single Market Data.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Independent Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Independent Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.singleMarketData)\n setup(templateClone, target, exchange, 'Exchange Data Products', 'singleMarketData')\n templateClone.payload.referenceParent = market\n\n return templateClone\n }\n}","id":"255fc8f9-413a-4334-b6f5-d944d57fb27c","savedPayload":{"position":{"x":41539.572203582546,"y":28677.555510830884},"targetPosition":{"x":41578.71473951283,"y":29277.75973906997},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"4dd2897c-d7e2-4fa2-a92f-4d0cf49a6370","savedPayload":{"position":{"x":41578.71473951283,"y":29277.75973906997},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Back WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"169aa57f-d46b-4979-b8a2-df6ba21eabc6","savedPayload":{"position":{"x":40971.62547458223,"y":28704.67815778764},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"41351410-0495-4419-8a78-babed3a2f25c","savedPayload":{"position":{"x":40739.2805936536,"y":28612.68448755448},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"fc43d350-5a2e-4948-8279-6444a15b39ed","savedPayload":{"position":{"x":40648.47472250254,"y":28380.33960662585},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"8f8c543a-5add-460f-9767-06cc069e142b","savedPayload":{"position":{"x":40749.374350272024,"y":28158.088482315645},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"0497555c-1024-4c2c-8be4-9c8917bed11f","savedPayload":{"position":{"x":40971.62547458223,"y":28380.33960662585},"targetPosition":{"x":41034.51136563778,"y":28696.544935681453},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"5d4fb545-1392-4651-8f54-32b3b0b2278f","savedPayload":{"position":{"x":41035.68589463498,"y":28694.595676926525},"targetPosition":{"x":41161.48169175592,"y":29327.013855736088},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"726cd3f8-cfce-4e06-8e13-f4b2523e6e57","savedPayload":{"position":{"x":41772.65093280171,"y":28646.807955048214},"targetPosition":{"x":41890.128901652744,"y":29236.71585692822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"d5bc03d5-baeb-4ab0-91a6-cc61d0122474","savedPayload":{"position":{"x":41890.12930892233,"y":28635.18613310936},"targetPosition":{"x":41890.128901652744,"y":29236.71585692822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Backtesting Session'\n return templateClone\n }\n}","id":"5201c9bb-45d3-4066-970d-56d13eb6905e","savedPayload":{"position":{"x":42007.53909316859,"y":28646.791845625325},"targetPosition":{"x":41890.128901652744,"y":29236.71585692822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"caa968ef-9a38-4fb4-998f-cb7e27a3eedd","savedPayload":{"position":{"x":41890.128901652744,"y":29236.71585692822},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Paper WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"fe43a58c-8d97-4f98-ba33-6328c1bcb7a7","savedPayload":{"position":{"x":41557.474267378886,"y":28704.538380895512},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"447096dc-d9b3-472d-985e-7810458651d2","savedPayload":{"position":{"x":41328.36437304013,"y":28614.669001956987},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"87f61b2b-cd47-42db-b8a8-2d2b84c30712","savedPayload":{"position":{"x":41238.701960314706,"y":28385.55910761823},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"fe0ed876-65d6-488c-8572-86708cd2f4b8","savedPayload":{"position":{"x":41328.657032916315,"y":28156.74187315566},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"4844333d-4238-412b-b2f3-403d78d1aaa8","savedPayload":{"position":{"x":41557.474267378886,"y":28385.55910761823},"targetPosition":{"x":41536.37709492533,"y":28707.266395142844},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"9bf71a35-9ee8-4679-89c7-e9894c3963cd","savedPayload":{"position":{"x":41538.15110500089,"y":28705.54204056541},"targetPosition":{"x":41495.97864513366,"y":29348.96944058102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f7b51fbd-c90d-4b5a-9242-9de2a40f4733","savedPayload":{"position":{"x":42240.40982433613,"y":28677.441521776378},"targetPosition":{"x":42201.34918584835,"y":29277.69269398605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"b7adb316-4430-4933-80ef-c8e239a947fc","savedPayload":{"position":{"x":42356.951494870984,"y":28696.641898991125},"targetPosition":{"x":42201.34918584835,"y":29277.69269398605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Paper Trading Session'\n return templateClone\n }\n}","id":"09fd8aa3-006f-4c13-8084-78da7f7ed4ca","savedPayload":{"position":{"x":42467.48260549182,"y":28738.277097634553},"targetPosition":{"x":42201.34918584835,"y":29277.69269398605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"ce8531c2-42df-4c54-b4ea-48d7ca9861e5","savedPayload":{"position":{"x":42201.34918584835,"y":29277.69269398605},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Live WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"22bcc102-5751-444e-a609-bf050743c987","savedPayload":{"position":{"x":42124.2954678251,"y":28863.166820214137},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"a97553e6-85bd-4f7d-b7a3-6235e540e1a8","savedPayload":{"position":{"x":41899.82203310804,"y":28765.34236295704},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"6adfe7ca-0353-4506-a1ac-09bd6b6a7c86","savedPayload":{"position":{"x":41803.41381185934,"y":28540.86892823998},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"9136e76e-e3e4-48c6-a6bd-e1a64176c7a3","savedPayload":{"position":{"x":41897.44019113757,"y":28314.01365155245},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"f267f52b-25be-4822-90b7-88bf3121d009","savedPayload":{"position":{"x":42124.2954678251,"y":28540.86892823998},"targetPosition":{"x":42020.66210112073,"y":28846.1629818523},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"a095996a-c30e-45e8-8598-7b6010b2095b","savedPayload":{"position":{"x":42020.66210112073,"y":28846.1629818523},"targetPosition":{"x":41813.395367712,"y":29456.751089076944},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"9df1f12f-0030-4c41-98e2-d07482ff8d22","savedPayload":{"position":{"x":42684.61234689323,"y":28828.20083898273},"targetPosition":{"x":42491.49072073263,"y":29397.8621330702},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"b5554d40-5014-45ec-b996-e74e05c5e872","savedPayload":{"position":{"x":42792.21171122186,"y":28876.91139330717},"targetPosition":{"x":42491.49072073263,"y":29397.8621330702},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Live Trading Session'\n return templateClone\n }\n}","id":"ade4b7d5-1066-43fa-b6af-e2d1213c4d14","savedPayload":{"position":{"x":42888.19927444742,"y":28945.736615872695},"targetPosition":{"x":42491.49072073263,"y":29397.8621330702},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"3e9a2343-49bb-43d5-adda-0072b01313f1","savedPayload":{"position":{"x":42491.49072073263,"y":29397.8621330702},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Forward WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"b03fa95d-fb4d-4cef-9590-70bcf0034123","savedPayload":{"position":{"x":42492.63963403888,"y":28500.7397102676},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"352b1a8a-1d74-44ff-b9f3-411f337db931","savedPayload":{"position":{"x":42710.36396962775,"y":28492.07047492605},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"c7ed2f21-cd86-44fe-a946-1dc00e5f8fb3","savedPayload":{"position":{"x":42891.7169300037,"y":28613.377753949273},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"13d4f731-4ee0-4a24-810a-f4760432e33e","savedPayload":{"position":{"x":42967.139262708275,"y":28818.147267745622},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"a70a92df-4abe-421d-92b2-ef70a756441c","savedPayload":{"position":{"x":42636.198275976094,"y":28799.555013396635},"targetPosition":{"x":42436.14135357761,"y":29099.04809298164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"3a96fe18-f607-4a4e-9921-31b507475885","savedPayload":{"position":{"x":42438.1614413147,"y":29100.30420086799},"targetPosition":{"x":42080.37565816827,"y":29635.83788162232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f6114149-90fb-45a7-a42a-6ab052a9980a","savedPayload":{"position":{"x":43074.64510700322,"y":29088.795420231665},"targetPosition":{"x":42740.63494825482,"y":29589.031414236302},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"315b67c6-84cf-4370-a4fd-a8209c2532c9","savedPayload":{"position":{"x":43165.96984719894,"y":29163.695538605894},"targetPosition":{"x":42740.63494825482,"y":29589.031414236302},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Forward Testing Session'\n return templateClone\n }\n}","id":"8635fe81-b9b5-4a95-a6d0-950584c2787c","savedPayload":{"position":{"x":43240.87263989456,"y":29255.01973980808},"targetPosition":{"x":42740.63494825482,"y":29589.031414236302},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"3bb7924d-49b4-4748-83f5-5067dd8f3ef5","savedPayload":{"position":{"x":42740.63494825482,"y":29589.031414236302},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Testing Environment Task Manager","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Paper & Backtesting Sessions","tasks":[{"type":"Task","name":"Paper Trading WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Paper Trading Session","name":"Paper WHB","code":"{\n\"folderName\": \"Paper-WHB\"\n}","parameters":{"type":"Parameters","name":"Parameters","baseAsset":{"type":"Base Asset","name":"","code":"{ \n\"initialBalance\": 0.01,\n\"minimumBalance\": 0.005\n}","id":"7d2a4605-5be7-4b12-9761-0087ce656080","savedPayload":{"position":{"x":43314.347355679805,"y":27886.180495266573},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"quotedAsset":{"type":"Quoted Asset","name":"","code":"{\n\"initialBalance\": 0\n}","id":"46c16e25-d21d-4ca8-a528-ae7bf85e9d3f","savedPayload":{"position":{"x":43270.11105487198,"y":27610.06167977048},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"finalDatetime\": \"2020-12-31\"\n}","id":"e645ba22-2ba3-4c87-9d62-748477e055dc","savedPayload":{"position":{"x":43458.610110666305,"y":27403.132156057905},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-min\"\n}","id":"9b0e2fa1-fb9e-4854-9657-7157843a0a56","savedPayload":{"position":{"x":43737.762993615695,"y":27421.44478260165},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"072fc7e2-ca96-4c0a-963c-d8697f26a12c","savedPayload":{"position":{"x":43897.65037204082,"y":27651.219207475497},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"New Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"28139c87-d8fb-49e6-92ce-f826f0ef32d3","savedPayload":{"position":{"x":43817.740726622455,"y":27919.157103391895},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"0e80ef27-72f6-42e9-98bd-2266ad8d0902","savedPayload":{"position":{"x":43579.28928007096,"y":27702.1138119903},"targetPosition":{"x":43558.15104818431,"y":28023.96596801331},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"43096f38-2d07-4536-a8de-c1d6dc52e5af","savedPayload":{"position":{"x":43558.15104818431,"y":28023.96596801331},"targetPosition":{"x":43537.2392391117,"y":28345.180697797186},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"h"},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","id":"c169326d-c842-452b-a9b7-e8658e31dcd4","savedPayload":{"position":{"x":43826.45361245783,"y":28203.054822784812},"targetPosition":{"x":43537.2392391117,"y":28345.180697797186},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"67daf900-abb9-4d94-b86b-85ec5820c311","savedPayload":{"position":{"x":43537.2392391117,"y":28345.180697797186},"targetPosition":{"x":43358.19739378843,"y":28612.949684445317},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"e1d9b766-0b00-478d-a179-b4d030811a17","savedPayload":{"position":{"x":43358.19739378843,"y":28612.949684445317},"targetPosition":{"x":43148.07377003864,"y":28927.454634128168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"10914881-2af7-4326-9a48-eb50a5b83b21","savedPayload":{"position":{"x":43148.247239305325,"y":28927.248338917063},"targetPosition":{"x":42790.1695410171,"y":29463.444388464814},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Backtesting WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Backtesting Session","name":"Back WHB","code":"{\n\"folderName\": \"Back-WHB\"\n}","parameters":{"type":"Parameters","name":"Parameters","baseAsset":{"type":"Base Asset","name":"","code":"{\n\"initialBalance\": 1,\n\"minimumBalance\": 0.5\n}","id":"58289171-cb5f-482c-b5f6-445eb6dd33bf","savedPayload":{"position":{"x":44036.02698339722,"y":28363.366059104726},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"quotedAsset":{"type":"Quoted Asset","name":"","code":"{}","id":"59fd49b3-e491-4029-9dae-12cf0a0e48e5","savedPayload":{"position":{"x":44135.88796016322,"y":28102.124337260877},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"initialDatetime\": \"2019-01-01\",\n\"finalDatetime\": \"2020-12-31\"\n}","id":"7bd10192-7a52-4995-98aa-e16129827ca8","savedPayload":{"position":{"x":44402.36557610739,"y":28017.15274644697},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-hs\"\n}","id":"59764b94-aa9f-462a-be17-5b110e18609a","savedPayload":{"position":{"x":44635.11601984425,"y":28172.691104702066},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"f72c1a53-9e2e-49d5-a07d-90d7ab53c6cf","savedPayload":{"position":{"x":44658.68970970164,"y":28451.428632796116},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"3f249546-8932-42c0-a779-b4142cb03d17","savedPayload":{"position":{"x":44455.41819917701,"y":28643.69425984277},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2c70a07e-67d1-4157-8524-448842327782","savedPayload":{"position":{"x":44357.202378679365,"y":28336.579675132554},"targetPosition":{"x":44178.11719301108,"y":28604.57168539026},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"58ad6731-33c6-4a8f-8bfd-fbd40d7aa8d0","savedPayload":{"position":{"x":44178.11719301108,"y":28604.57168539026},"targetPosition":{"x":43999.15712266918,"y":28872.29629503109},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","id":"3a49b8dd-94b8-4b8f-9c0d-a140ba4526a8","savedPayload":{"position":{"x":44320.91170982664,"y":28893.613213044515},"targetPosition":{"x":43999.15712266918,"y":28872.29629503109},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"649950c4-79f9-4d35-ba72-a96d67fb9278","savedPayload":{"position":{"x":43999.15712266918,"y":28872.29629503109},"targetPosition":{"x":43710.28876756263,"y":29014.513498728833},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"ef610792-0e38-40b6-8d28-41b6b8d76cb7","savedPayload":{"position":{"x":43710.28876756263,"y":29014.513498728833},"targetPosition":{"x":43370.93380625043,"y":29181.70054572794},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"13ceeef0-8a9f-4da9-a4ce-43bc32323c47","savedPayload":{"position":{"x":43368.53019880899,"y":29178.408421637265},"targetPosition":{"x":42790.1695410171,"y":29463.444388464814},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}}],"id":"e4b004e5-d078-435b-896e-bcbec6684a4c","savedPayload":{"position":{"x":42623.940516318515,"y":29608.370805879204},"targetPosition":{"x":42301.51227538709,"y":29890.981539114837},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"d1632ba7-4823-4174-9caf-8979360c7399","savedPayload":{"position":{"x":43383.9329297536,"y":29441.46216514682},"targetPosition":{"x":42931.807057773796,"y":29838.171591076367},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"cce4bcd5-fa0e-4dda-82a7-28cbf76432a4","savedPayload":{"position":{"x":43452.759505915485,"y":29537.44718063196},"targetPosition":{"x":42931.807057773796,"y":29838.171591076367},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Testing Environment","name":"New Testing Environment","id":"2ae685a1-25c6-4bd5-b559-d83e373e8d45"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Testing Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Testing Environment.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n \n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n if (process.session !== undefined) {\n if (process.session.parameters !== undefined) {\n if (process.session.parameters.baseAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.baseAsset, superAction.payload.parentNode.baseAsset)\n }\n if (process.session.parameters.quotedAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.quotedAsset, superAction.payload.parentNode.quotedAsset)\n }\n\n function findExchangeAccountAsset(taskAsset, marketAsset) {\n for (let k = 0; k < superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts.length; k++) {\n let userAccount = superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[k]\n let found = false\n if (userAccount.userAssets !== undefined) {\n for (let m = 0; m < userAccount.userAssets.assets.length; m++) {\n let asset = userAccount.userAssets.assets[m]\n if (asset.payload.referenceParent !== undefined) {\n if (asset.payload.referenceParent.id === marketAsset.payload.referenceParent.id) {\n taskAsset.payload.referenceParent = asset\n found = true\n break\n }\n }\n }\n }\n if (found === true) { break }\n }\n }\n }\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"49b39b49-1d4c-405c-a9f8-955e0ecc9a71","savedPayload":{"position":{"x":43501.47288880676,"y":29645.046577239078},"targetPosition":{"x":42931.807057773796,"y":29838.171591076367},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"414cdf9f-dc5b-4c23-ac5c-0e7a098b2143","savedPayload":{"position":{"x":42931.807057773796,"y":29838.171591076367},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Production Environment Task Manager","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Fwd & Live Sessions","tasks":[{"type":"Task","name":"Fwd Testing WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Forward Testing Session","name":"Fwd WHB","code":"{\n\"folderName\": \"Fwd-WHB\",\n\"balancePercentage\": 10\n}","parameters":{"type":"Parameters","name":"Parameters","baseAsset":{"type":"Base Asset","name":"","code":"{ \n\"initialBalance\": 0.01,\n\"minimumBalance\": 0.005\n}","id":"19a13131-9554-43ef-860b-d994b9804a0e","savedPayload":{"position":{"x":43949.02209402912,"y":28514.744495850893},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"quotedAsset":{"type":"Quoted Asset","name":"","code":"{\n\"initialBalance\": 0\n}","id":"00ee3b70-7e94-4b71-aa64-05f6a699b080","savedPayload":{"position":{"x":43977.81112139866,"y":28236.59236923798},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"finalDatetime\": \"2020-12-31\"\n}","id":"8d94c05a-b82d-4fd9-8552-dcf19fb01ba1","savedPayload":{"position":{"x":44213.44333414086,"y":28085.505562610808},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-min\"\n}","id":"e6e657e8-1049-40e7-aeec-b57429e34a59","savedPayload":{"position":{"x":44478.339217712455,"y":28175.492208004023},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"57de0f1c-8cef-462a-bb88-764cfec67516","savedPayload":{"position":{"x":44573.309327218114,"y":28438.61215850976},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"New Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"70c32855-5085-4e45-be3c-cfdb212ef266","savedPayload":{"position":{"x":44426.715336862566,"y":28676.91519870268},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c5989e16-cde7-414d-9296-e28d805e216a","savedPayload":{"position":{"x":44252.09762841825,"y":28406.000583471792},"targetPosition":{"x":44148.7422061206,"y":28711.062917859083},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"47651cad-aee5-405e-9d40-b4787c49d09e","savedPayload":{"position":{"x":44148.7422061206,"y":28711.062917859083},"targetPosition":{"x":44045.11308194365,"y":29016.158202849896},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","keyInstance":{"type":"Key Instance","name":"","id":"5677d06d-c674-476d-a9a9-fc231616f478","savedPayload":{"position":{"x":44677.4538514425,"y":28891.11030273736},"targetPosition":{"x":44361.54848844125,"y":28953.513309627917},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ac49275e-4fe1-480f-be02-37665547a777","savedPayload":{"position":{"x":44361.54848844125,"y":28953.513309627917},"targetPosition":{"x":44045.11308194365,"y":29016.158202849896},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"57deee32-22b7-4eec-8c9a-9b971d1aeb1a","savedPayload":{"position":{"x":44045.11308194365,"y":29016.158202849896},"targetPosition":{"x":43803.03273271357,"y":29228.27863694877},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"8910dee0-b699-4a35-85ee-a2b3bbdac18e","savedPayload":{"position":{"x":43803.03273271357,"y":29228.27863694877},"targetPosition":{"x":43518.575577642056,"y":29477.69782587427},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"70fd8717-56d5-4ad8-b6ed-3050decedf19","savedPayload":{"position":{"x":43511.58765394764,"y":29481.161545691997},"targetPosition":{"x":43028.08397698985,"y":29905.30941876938},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Live Trading WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Live Trading Session","name":"Live Trading WHB","code":"{\n\"folderName\": \"Live-WHB\"\n}","parameters":{"type":"Parameters","name":"Parameters","baseAsset":{"type":"Base Asset","name":"","code":"{ \n\"initialBalance\": 0.01,\n\"minimumBalance\": 0.005\n}","id":"1c0d2a17-cfd5-4eb4-8322-388ad7472fea","savedPayload":{"position":{"x":44511.69115776187,"y":29163.28766291385},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"quotedAsset":{"type":"Quoted Asset","name":"","code":"{\n\"initialBalance\": 0\n}","id":"43d84134-6e65-4ba9-93fb-0d8cb705e90a","savedPayload":{"position":{"x":44675.41944631209,"y":28937.14672767146},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"finalDatetime\": \"2020-12-31\"\n}","id":"16b7de55-dd83-4d7b-a1f8-45f28200a6f6","savedPayload":{"position":{"x":44954.1838102591,"y":28924.144732198314},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-min\"\n}","id":"8c2d281c-7cd7-4f3e-8411-777eaec18ed5","savedPayload":{"position":{"x":45138.344553177376,"y":29134.142576562248},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"dc21103a-37a9-4c67-a456-a3d35c14e9ad","savedPayload":{"position":{"x":45089.04722701449,"y":29408.829825358807},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"New Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"c0a305e0-3a8b-428e-b005-117bd35270dc","savedPayload":{"position":{"x":44843.50938494748,"y":29541.60389632369},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"325c8283-9e06-4711-9791-49994e1eb24f","savedPayload":{"position":{"x":44827.797086714076,"y":29220.397958400925},"targetPosition":{"x":44586.3641707821,"y":29432.324069027018},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"e70a2d0f-329a-410e-b3ed-fbf1968cac32","savedPayload":{"position":{"x":44586.3641707821,"y":29432.324069027018},"targetPosition":{"x":44344.60660428931,"y":29644.1656163963},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","keyInstance":{"type":"Key Instance","name":"","id":"3fa5a1c3-ec84-4335-b318-7ed4c6e412a9","savedPayload":{"position":{"x":44953.33353749047,"y":29851.30281583057},"targetPosition":{"x":44649.290571908976,"y":29747.76088866544},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Exchange Account Key","name":"Key 1","id":"d2893836-c3d5-449f-a132-971542ef0c0d"}}},"id":"a6c24af9-77fd-4179-bb83-22928acce259","savedPayload":{"position":{"x":44649.290571908976,"y":29747.76088866544},"targetPosition":{"x":44344.60660428931,"y":29644.1656163963},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"6053e090-2c8e-4c1b-8eb4-eb3032f5cb9d","savedPayload":{"position":{"x":44344.60660428931,"y":29644.1656163963},"targetPosition":{"x":44029.65068525114,"y":29706.589257726668},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"67b86977-56e8-4cb9-aa4f-0ae1b8d6d9f3","savedPayload":{"position":{"x":44029.65068525114,"y":29706.589257726668},"targetPosition":{"x":43658.77696384793,"y":29780.18960006669},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"8b8401d2-fbd3-4514-9a24-6236941f8572","savedPayload":{"position":{"x":43658.94710506227,"y":29780.009588673463},"targetPosition":{"x":43028.08397698985,"y":29905.30941876938},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"122a1f16-06c2-4e63-9c9b-0b1b6532ef6e","savedPayload":{"position":{"x":42836.228026148114,"y":30000.0708266346},"targetPosition":{"x":42451.60692532848,"y":30189.593514413704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"l"}}},"id":"7154ffab-e458-47a9-98ab-716d7db899c2","savedPayload":{"position":{"x":43591.39888865664,"y":29862.167594940413},"targetPosition":{"x":43051.97981248752,"y":30128.30519050224},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"efbaa91d-f097-4fda-b373-8b9394dae215","savedPayload":{"position":{"x":43633.03656791882,"y":29972.696256647498},"targetPosition":{"x":43051.97981248752,"y":30128.30519050224},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Production Environment","name":"New Production Environment","id":"32656457-f603-416a-b805-546642094dc4"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Production Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Production Environment.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n\n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n if (process.marketReference.keyInstance !== undefined) {\n process.marketReference.keyInstance.payload.referenceParent = key\n }\n if (process.session !== undefined) {\n if (process.session.parameters !== undefined) {\n if (process.session.parameters.baseAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.baseAsset, superAction.payload.parentNode.baseAsset)\n }\n if (process.session.parameters.quotedAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.quotedAsset, superAction.payload.parentNode.quotedAsset)\n }\n\n function findExchangeAccountAsset(taskAsset, marketAsset) {\n for (let k = 0; k < superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts.length; k++) {\n let userAccount = superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[k]\n let found = false\n if (userAccount.userAssets !== undefined) {\n for (let m = 0; m < userAccount.userAssets.assets.length; m++) {\n let asset = userAccount.userAssets.assets[m]\n if (asset.payload.referenceParent !== undefined) {\n if (asset.payload.referenceParent.id === marketAsset.payload.referenceParent.id) {\n taskAsset.payload.referenceParent = asset\n found = true\n break\n }\n }\n }\n }\n if (found === true) { break }\n }\n }\n }\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"7504b088-dc0b-4a67-b673-150524eb3430","savedPayload":{"position":{"x":43652.240225669295,"y":30089.23818002004},"targetPosition":{"x":43051.97981248752,"y":30128.30519050224},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"aaef47a3-030f-4bba-a297-72973f84a6a7","savedPayload":{"position":{"x":43051.97981248752,"y":30128.30519050224},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Masters Data Mining Tasks","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Masters","tasks":[{"type":"Task","name":"OHKCVs","bot":{"type":"Sensor Bot Instance","name":"Exchange Raw Data","code":" {\n \"repo\": \"Exchange-Raw-Data-Sensor-Bot\",\n \"startDate\": \"2015-01-01\"\n }","processes":[{"type":"Sensor Process Instance","name":"Exchange Raw Data Historic-OHLCVs","marketReference":{"type":"Market Reference","name":"","id":"fa969ad9-f967-4c42-9a7b-e3ca5c53a2a4","savedPayload":{"position":{"x":44563.752766473845,"y":28726.51204419318},"targetPosition":{"x":44505.18351145028,"y":29022.185768977342},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"7321618d-f188-4909-ae51-bff16e83e85c","savedPayload":{"position":{"x":44505.18351145028,"y":29022.185768977342},"targetPosition":{"x":44446.73000839822,"y":29317.919871736813},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Historic-OHLCVs","id":"5eb11d6a-022e-4f60-97c4-d6cf90b249dc"}}}],"id":"66324e06-a787-4389-ba0a-f6158224532e","savedPayload":{"position":{"x":44446.73000839822,"y":29317.919871736813},"targetPosition":{"x":44388.44322497823,"y":29613.907569482504},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"02b2b5ef-8fbb-4234-847b-9e9381e39e40","savedPayload":{"position":{"x":44388.44322497823,"y":29613.907569482504},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Candles & Volumes","bot":{"type":"Indicator Bot Instance","name":"Candles Volumes","code":" {\n \"repo\": \"Candles-Volumes-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"Candles Volumes Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"65a2cdb9-af06-458b-ae81-52a922d32874","savedPayload":{"position":{"x":44967.25960209239,"y":28881.73820503715},"targetPosition":{"x":44870.441375474875,"y":29167.311094532386},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"fe3e9758-12d8-46da-a7e9-968a340f0ae5","savedPayload":{"position":{"x":44870.441375474875,"y":29167.311094532386},"targetPosition":{"x":44773.69751260277,"y":29452.87114374833},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"9f683143-a33f-42c1-9fa8-c601f01ee4e1"}}},{"type":"Indicator Process Instance","name":"Candles Volumes Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"5ba98b0e-e71d-4c91-a3b2-ca98f7bf3e1b","savedPayload":{"position":{"x":45453.09059939077,"y":28856.68518068597},"targetPosition":{"x":45113.42999916721,"y":29154.70174934311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"e3fe5518-65d4-4413-9d1f-624025766f97","savedPayload":{"position":{"x":45113.42999916721,"y":29154.70174934311},"targetPosition":{"x":44773.69751260277,"y":29452.87114374833},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"980ceb1d-7be6-454f-90d9-01ba1fd4a65d"}}}],"id":"80415df6-6aca-42d5-b91d-6d5d6326f72f","savedPayload":{"position":{"x":44773.69751260277,"y":29452.87114374833},"targetPosition":{"x":44606.29852269913,"y":29703.881918996256},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"a2a8253d-ea54-431a-a623-627a9d80c96d","savedPayload":{"position":{"x":44606.29852269913,"y":29703.881918996256},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Candles Channels","bot":{"type":"Indicator Bot Instance","name":"Candles Channels","code":"{}","processes":[{"type":"Indicator Process Instance","name":"Candle Channels Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Binance BTC/USDT","id":"cb0330ef-a176-4e88-b09e-17c5c0ed8893","savedPayload":{"position":{"x":45421.3393568106,"y":29249.255430321416},"targetPosition":{"x":45222.451918474486,"y":29475.929696456333},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"5489751f-5ce4-40d0-9854-5bcfdc810269"}}},"id":"606c5e74-aa46-47d9-82c9-e1ff4a627ebe","savedPayload":{"position":{"x":45222.451918474486,"y":29475.929696456333},"targetPosition":{"x":45023.650296285596,"y":29702.579362584125},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"2ab8c00c-8e15-4bdc-87fa-291c8d4edadb"}}},{"type":"Indicator Process Instance","name":"Candle Channels Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Binance BTC/USDT","id":"de1108b1-a773-4d54-b32b-b2931011f115","savedPayload":{"position":{"x":45879.664842377,"y":29411.96516858365},"targetPosition":{"x":45451.73624737675,"y":29557.237066035806},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"5489751f-5ce4-40d0-9854-5bcfdc810269"}}},"id":"6c0075a2-e741-45b7-8b6c-66d2faf95ad8","savedPayload":{"position":{"x":45451.73624737675,"y":29557.237066035806},"targetPosition":{"x":45023.650296285596,"y":29702.579362584125},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"0de6dd1f-5bca-4f66-a5db-cd068dd4190f"}}}],"id":"d29785c9-1789-45f5-88a1-17fa13a383bb","savedPayload":{"position":{"x":45023.650296285596,"y":29702.579362584125},"targetPosition":{"x":44772.77564042518,"y":29870.23031124894},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"34ef19e0-5e75-4108-86e5-276f213deea5","savedPayload":{"position":{"x":44772.77564042518,"y":29870.23031124894},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},{"type":"Task","name":"Bollinger Bands & Percentage Bandwidth","bot":{"type":"Indicator Bot Instance","name":"Bollinger Bands","code":" {\n \"repo\": \"Bollinger-Bands-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"Bollinger Bands Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"936109e1-9d5f-4738-9eee-26e98c6675a9","savedPayload":{"position":{"x":45699.89387085827,"y":29762.487102244177},"targetPosition":{"x":45429.335053302355,"y":29895.69561846699},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"69c2cfe4-3a79-45d0-8742-ff3f368431f6","savedPayload":{"position":{"x":45429.335053302355,"y":29895.69561846699},"targetPosition":{"x":45158.858744477104,"y":30028.883442023704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"e598ee86-1e18-4aa1-95c2-64aaf66776ca"}}},{"type":"Indicator Process Instance","name":"Bollinger Bands Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"75d8a16d-5ff3-4771-b32a-bf5ea071e7fc","savedPayload":{"position":{"x":46061.00454182307,"y":30087.712394580292},"targetPosition":{"x":45610.02425587317,"y":30058.313770154724},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"c437054a-3447-486b-ab42-b267b800f96a","savedPayload":{"position":{"x":45610.02425587317,"y":30058.313770154724},"targetPosition":{"x":45158.858744477104,"y":30028.883442023704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"941a0543-0cc0-4a5d-a782-728d2835c100"}}}],"id":"9604d5b6-af02-4ac3-91aa-fa82dcd316bf","savedPayload":{"position":{"x":45158.858744477104,"y":30028.883442023704},"targetPosition":{"x":44862.83944658449,"y":30087.58329134732},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"ed9d924d-7a8b-46da-a4e9-eb121329fe49","savedPayload":{"position":{"x":44862.83944658449,"y":30087.58329134732},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Bollinger Channels & Sub-Channels","bot":{"type":"Indicator Bot Instance","name":"Bollinger Channels","code":" {\n \"repo\": \"Bollinger-Channels-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"Bollinger Channels Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"6e4bd29a-ffe6-4bbd-a114-62a0efe82a12","savedPayload":{"position":{"x":45760.6488820697,"y":30342.828489267125},"targetPosition":{"x":45459.70100509999,"y":30362.234023108005},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"b3c5ca8e-cc9d-4662-96cb-82d7c5801f67","savedPayload":{"position":{"x":45459.70100509999,"y":30362.234023108005},"targetPosition":{"x":45158.82769546795,"y":30381.603232805704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"28be432d-80ca-46b3-859a-55541e24b2b4"}}},{"type":"Indicator Process Instance","name":"Bollinger Channels Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"d6995a0d-dffd-4213-b159-1919fa35f1b8","savedPayload":{"position":{"x":45969.52778588621,"y":30781.448959775782},"targetPosition":{"x":45564.269191446634,"y":30581.63665626878},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"d640b042-3b50-4f02-961a-9f4e9aab1015","savedPayload":{"position":{"x":45564.269191446634,"y":30581.63665626878},"targetPosition":{"x":45158.82769546795,"y":30381.603232805704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"bc15b49d-4547-4bac-a28b-193eb0158fe3"}}}],"id":"88c5bffa-e658-4a99-89b3-b3b03d40d2e6","savedPayload":{"position":{"x":45158.82769546795,"y":30381.603232805704},"targetPosition":{"x":44862.82546003235,"y":30322.600699455408},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"10972cbd-be85-4344-bf34-8aab4dc3a23b","savedPayload":{"position":{"x":44862.82546003235,"y":30322.600699455408},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},{"type":"Task","name":"Webhooks","bot":{"type":"Sensor Bot Instance","name":"Webhooks","code":" {\n \"repo\": \"Webhooks-Sensor-Bot\"\n }","processes":[{"type":"Sensor Process Instance","name":"Webhooks Check-Webhook","marketReference":{"type":"Market Reference","name":"Binance BTC/USDT","id":"2ef284e2-813b-422b-91b1-fd9f4a3289dc","savedPayload":{"position":{"x":41234.40772073858,"y":30225.096264588887},"targetPosition":{"x":40812.801769173064,"y":30219.066258020608},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"5489751f-5ce4-40d0-9854-5bcfdc810269"}}},"id":"4b5cf18d-cd45-4a96-9117-8d5d7781b0ab","savedPayload":{"position":{"x":45274.248143438956,"y":30875.483495476296},"targetPosition":{"x":45023.527910627956,"y":30707.83217071608},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Check-Webhook","id":"69f37820-ab16-4e19-bfd8-386dd6e249a3"}}}],"id":"79548414-ae13-4e0f-99b6-fff96171bbaf","savedPayload":{"position":{"x":45023.527910627956,"y":30707.83217071608},"targetPosition":{"x":44772.69288254182,"y":30539.965434132333},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"5aa3a8bc-9fda-4e11-a56d-5655b148bfa7","savedPayload":{"position":{"x":44772.69288254182,"y":30539.965434132333},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Resistances & Supports","bot":{"type":"Indicator Bot Instance","name":"Resistances & Supports","code":" {\n \"repo\": \"\"\n }","processes":[{"type":"Indicator Process Instance","name":"Resistances and Supports Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"e28673ef-1555-4468-83f6-9011026ef257","savedPayload":{"position":{"x":45452.66805723561,"y":31553.156804953058},"targetPosition":{"x":45113.06001290915,"y":31255.234887033326},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"1d60f7c3-f431-43f0-aaf3-6b25c092a7d3","savedPayload":{"position":{"x":45113.06001290915,"y":31255.234887033326},"targetPosition":{"x":44773.34200526984,"y":30957.178797188724},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"5d9b2729-43fb-4e7e-aa4f-b8477c424e51"}}},{"type":"Indicator Process Instance","name":"Resistances and Supports Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"5c8850db-00c6-4607-b153-469534df6f68","savedPayload":{"position":{"x":44966.73206008909,"y":31528.118995498546},"targetPosition":{"x":44870.00048688255,"y":31242.66032093637},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f38aa087-739d-4c9c-ac81-c5eaf4750f6e","savedPayload":{"position":{"x":44870.00048688255,"y":31242.66032093637},"targetPosition":{"x":44773.34200526984,"y":30957.178797188724},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"be19d91b-9337-4f84-96b3-f4e54ea12df5"}}}],"id":"12391346-c72a-44d7-8574-17fd5ed22657","savedPayload":{"position":{"x":44773.34200526984,"y":30957.178797188724},"targetPosition":{"x":44606.10518045995,"y":30706.220760937824},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"39e297c4-fc60-4df1-a76d-c59f45b6a090","savedPayload":{"position":{"x":44606.10518045995,"y":30706.220760937824},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"8e0023df-5beb-4187-a53b-908c363861f9","savedPayload":{"position":{"x":44273.186396693905,"y":30204.885859141745},"targetPosition":{"x":43682.65778612944,"y":30322.206497874296},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"j"}}},"id":"930ad389-2e02-405d-a608-628df967e702","savedPayload":{"position":{"x":43682.89779132531,"y":30322.114446203646},"targetPosition":{"x":43092.9570586135,"y":30439.530985154168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"216101a4-697c-41c6-989c-a5f8f181c624","savedPayload":{"position":{"x":43694.50778488079,"y":30439.5301984526},"targetPosition":{"x":43092.9570586135,"y":30439.530985154168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Mining","name":"New Data Mining","id":"7449a608-32ca-4285-94d1-c96791e2817f"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Data Mining') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n \n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"c1cbecc9-8340-4435-a0ab-140334ba104d","savedPayload":{"position":{"x":43682.8911800849,"y":30556.999036454978},"targetPosition":{"x":43092.9570586135,"y":30439.530985154168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"ddd80520-a052-464b-a7b8-23b72387086f","savedPayload":{"position":{"x":43092.9570586135,"y":30439.530985154168},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Sparta Data Mining Tasks","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Sparta","tasks":[{"type":"Task","name":"SMA","bot":{"type":"Indicator Bot Instance","name":"SMA","code":" {\n\"repo\": \"SMA-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"SMA Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"592304dd-b412-4f42-9f24-1c2b6305538a","savedPayload":{"position":{"x":44071.61797125626,"y":29269.179122287296},"targetPosition":{"x":44017.22604407985,"y":29586.166432086655},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"4829d508-b32b-402c-8d27-4493549196b5","savedPayload":{"position":{"x":44017.22604407985,"y":29586.166432086655},"targetPosition":{"x":43962.84190684965,"y":29903.09502706654},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"456269f6-9849-4d5f-9c8c-7cd7d1eef297"}}},{"type":"Indicator Process Instance","name":"SMA Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"4de8af37-754b-4178-9960-49f8dd9fea12","savedPayload":{"position":{"x":45168.8488344655,"y":29458.147255078686},"targetPosition":{"x":44565.86589926591,"y":29680.585441818144},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"5f34959e-a63c-4298-8323-a16f75e23431","savedPayload":{"position":{"x":44565.86589926591,"y":29680.585441818144},"targetPosition":{"x":43962.84190684965,"y":29903.09502706654},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"333ae5d7-7ec1-4f08-b206-91e3b4f2c99f"}}}],"id":"6db29c31-152b-472a-852e-0e4a36bec524","savedPayload":{"position":{"x":43962.84190684965,"y":29903.09502706654},"targetPosition":{"x":43551.90647643782,"y":30397.266512500442},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"20c34f88-6b58-474b-b69d-02712765c0db","savedPayload":{"position":{"x":43551.90647643782,"y":30397.266512500442},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"EMA","bot":{"type":"Indicator Bot Instance","name":"EMA","code":" {\n\"repo\": \"EMA-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"EMA Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"64c7ca91-01ce-4dab-8b29-7cfa8e1f19a4","savedPayload":{"position":{"x":44848.06740265459,"y":30126.954299288456},"targetPosition":{"x":44617.64305938516,"y":30351.280902615003},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"31761ef4-c98b-4657-a37f-652eff5340ea","savedPayload":{"position":{"x":44617.64305938516,"y":30351.280902615003},"targetPosition":{"x":44387.27143213535,"y":30575.564915785017},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"278c34a7-199d-400e-8a06-087149e3d391"}}},{"type":"Indicator Process Instance","name":"EMA Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"7610e87b-a8c3-475c-a44f-665e26614ad8","savedPayload":{"position":{"x":45624.60027321795,"y":30924.208529781736},"targetPosition":{"x":45005.977709353945,"y":30749.876937252106},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"c4d61b48-9c23-47f1-937b-1729767a26f6","savedPayload":{"position":{"x":45005.977709353945,"y":30749.876937252106},"targetPosition":{"x":44387.27143213535,"y":30575.564915785017},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"943ea24f-ee24-4eae-9b4b-e8a73122b1c2"}}}],"id":"34656b7b-826e-4ebd-943a-407d8c91ea24","savedPayload":{"position":{"x":44387.27143213535,"y":30575.564915785017},"targetPosition":{"x":43764.266809147426,"y":30733.667438768847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"28be18c7-c387-435e-ae7d-dfeca201bce5","savedPayload":{"position":{"x":43764.266809147426,"y":30733.667438768847},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"MACD","bot":{"type":"Indicator Bot Instance","name":"MACD","code":" {\n\"repo\": \"MACD-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"MACD Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"4f7ed8bc-c0b7-4930-80c3-67c7d5378066","savedPayload":{"position":{"x":44971.81020849867,"y":31276.620146946167},"targetPosition":{"x":44653.49004123155,"y":31322.55467880252},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"9cc4175f-28ad-4307-a8d6-0df303a02be1","savedPayload":{"position":{"x":44653.49004123155,"y":31322.55467880252},"targetPosition":{"x":44335.23861007668,"y":31368.479242651025},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"44019efe-950a-4eec-a546-cd2ee26818b9"}}},{"type":"Indicator Process Instance","name":"MACD Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"3c7ff3c1-be17-4c7d-ba0a-1cc66a41970b","savedPayload":{"position":{"x":45131.12724051729,"y":32377.93040356555},"targetPosition":{"x":44733.22183307022,"y":31873.22574960204},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"a585a9e1-e18d-40b6-b364-75f01e840a4b","savedPayload":{"position":{"x":44733.22183307022,"y":31873.22574960204},"targetPosition":{"x":44335.23861007668,"y":31368.479242651025},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"8e5db945-8507-4489-9288-21cfa1e9301c"}}}],"id":"525c3ef8-145e-4919-bf2d-983db99a5cdb","savedPayload":{"position":{"x":44335.23861007668,"y":31368.479242651025},"targetPosition":{"x":43738.24708451567,"y":31130.308446359704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"b652ba10-fb7a-4a93-bd24-9500bb7d25ec","savedPayload":{"position":{"x":43738.24708451567,"y":31130.308446359704},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"RSI","bot":{"type":"Indicator Bot Instance","name":"RSI","code":" {\n\"repo\": \"RSI-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"RSI Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"002570f4-0254-46d4-ba77-c23598fd8e51","savedPayload":{"position":{"x":44395.607564219106,"y":32279.346270225502},"targetPosition":{"x":44111.06304717549,"y":32129.543698932317},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"16a0815f-b793-4ca8-aa09-78f9f447f110","savedPayload":{"position":{"x":44111.06304717549,"y":32129.543698932317},"targetPosition":{"x":43826.594538981204,"y":31979.768305805377},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"96f31416-5090-428a-be83-985a0b3ab275"}}},{"type":"Indicator Process Instance","name":"RSI Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"a1c6a73c-d518-478c-b1bb-cb1b0b4f08db","savedPayload":{"position":{"x":43876.85035087776,"y":33264.29116567567},"targetPosition":{"x":43851.71485258066,"y":32622.075786304355},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"123f248b-aa95-4d1c-a6c2-9fc70fc2b816","savedPayload":{"position":{"x":43851.71485258066,"y":32622.075786304355},"targetPosition":{"x":43826.594538981204,"y":31979.768305805377},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"f1177bcd-22dc-425b-8f30-878844aa82b8"}}}],"id":"007d0fe3-9637-4a4f-8078-730af1716e1b","savedPayload":{"position":{"x":43826.594538981204,"y":31979.768305805377},"targetPosition":{"x":43483.775300077,"y":31436.11470464454},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"da1cddfc-5d56-46e7-b14a-0b21127cf6e3","savedPayload":{"position":{"x":43483.775300077,"y":31436.11470464454},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}}],"id":"6907a34a-7247-4717-8be3-154ebc2cacfc","savedPayload":{"position":{"x":42927.695684739876,"y":30878.09191280621},"targetPosition":{"x":42499.798120847416,"y":30850.102649976},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"j"}}},"id":"ac392204-2e14-4744-a636-063c2bf461ae","savedPayload":{"position":{"x":43652.185746548756,"y":30789.964498250916},"targetPosition":{"x":43051.954111996405,"y":30750.842818950958},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"4d93f230-422c-4a10-8e6d-50381aa60aba","savedPayload":{"position":{"x":43632.969464539834,"y":30906.514370388715},"targetPosition":{"x":43051.954111996405,"y":30750.842818950958},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Mining","name":"New Data Mining","id":"7449a608-32ca-4285-94d1-c96791e2817f"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Data Mining') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n \n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"b2fdaf25-bbd2-476a-93ed-faf23b3bfa00","savedPayload":{"position":{"x":43591.30937899207,"y":31017.075000840647},"targetPosition":{"x":43051.954111996405,"y":30750.842818950958},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"83556c43-8483-48e3-be7e-36f9dbb70f08","savedPayload":{"position":{"x":43051.954111996405,"y":30750.842818950958},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Time Machine","templateStructure":{"type":"Template Structure","name":"New Template Structure","timeMachines":{"type":"Time Machine","name":"My Time Machine","code":"{}","timeScale":{"type":"Time Scale","name":"New Time Scale","code":"{\n \"fromDate\": \"2018-12-30T23:22:24.048Z\",\n \"toDate\": \"2019-01-10T19:44:56.900Z\",\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"acf304c4-443a-4a57-880e-13d330e754c4","savedPayload":{"position":{"x":44972.262947294315,"y":31118.718470440148},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"offset\": 0,\n \"minValue\": 3540,\n \"maxValue\": 4069.8,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"153feb9a-76e1-4838-ae3e-33cacd7ca646","savedPayload":{"position":{"x":45091.17064358791,"y":31222.70534719221},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{\"value\":\"24-hs\"}","id":"7890a24e-5bf5-450f-bb35-f23b3eb495f6","savedPayload":{"position":{"x":45178.77949805262,"y":31353.610402114664},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timelineCharts":[{"type":"Timeline Chart","name":"Indicators","layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 2,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"on\",\n \"showPanels\": false\n}","id":"38e64db1-376d-45de-a92c-a6fb25282507","savedPayload":{"position":{"x":45798.08358329995,"y":30783.47112133315},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Candles","id":"884a5d08-b607-4b2b-8e7a-8adeb7e8dd4a"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"9ad6d912-ff9c-45c8-bfce-bcd0d86e3977","savedPayload":{"position":{"x":45898.37673360377,"y":30788.25119234455},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Bollinger Bands","id":"68df01d8-d9a8-400c-aa76-8da44c5dc040"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"6c5e7c7f-a010-4d59-8299-d8d4534aa7f8","savedPayload":{"position":{"x":45996.05938571397,"y":30809.588522972816},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Bollinger Channels Objects","id":"632f0f98-eb2c-4820-9943-662a4903be81"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"41db7192-ec70-4f95-85dd-343bfa05258e","savedPayload":{"position":{"x":46088.671022938994,"y":30846.72421469604},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Bollinger Sub-Channels Objects","id":"88a1ca81-b317-4ab2-b651-c8fa566e8e28"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"13c5c367-3758-4f8f-883c-ba6f1ee3bef2","savedPayload":{"position":{"x":46173.797322491286,"y":30898.58369972401},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Candle-Stairs","id":"5979844b-35e7-4c04-938f-18e6156352a0"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"0fac6cd3-3172-4854-9ce2-f2827023f503","savedPayload":{"position":{"x":46249.17664768098,"y":30963.730987652525},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Popular SMAs","id":"5bce195f-7d08-4281-bb9d-ca1e9417068e"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"88c7874f-9372-4dc5-9bb1-491ca58ceded","savedPayload":{"position":{"x":46312.78393052083,"y":31040.384533238812},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base7 SMAs","id":"b9145946-1899-4fac-b531-47ed600873be"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"6570c568-8d96-427f-9dbe-822d0fe9f900","savedPayload":{"position":{"x":46362.900460538505,"y":31126.454059169817},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base11 SMAs","id":"c9daf265-f452-4372-a8a9-6ed9c0d7a0ed"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"a133263e-3eca-4d4a-b04e-8f40e5ec78ba","savedPayload":{"position":{"x":46398.167326095994,"y":31219.59432824167},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Popular EMAs","id":"fba1f18e-dd5b-48e6-82b1-ff04a624f090"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"32fcaa2a-af1d-4aa6-adf0-36dd24759ca4","savedPayload":{"position":{"x":46417.62588351845,"y":31317.267577006704},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base7 EMAs","id":"64f059bf-66e2-463c-aed1-800b5fa195ab"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"3d5d19b9-481d-4542-b279-06303aa9edef","savedPayload":{"position":{"x":46420.70567572493,"y":31416.809466474464},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base11 EMAs","id":"5b6e48e5-259f-44f3-b40e-a29982204c95"}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"04f846d4-f203-4eed-807d-214655d4551b","savedPayload":{"position":{"x":46407.31337137963,"y":31515.483822428057},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"10e19911-11b2-4b8d-86fa-b7a5de9f93e4","savedPayload":{"position":{"x":46377.87367543775,"y":31610.607629023725},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"5666253c-62f6-46f2-91fa-c19b44a5932a","savedPayload":{"position":{"x":46333.185880681754,"y":31699.591341637817},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"aae1b1f8-35b5-4c41-b5c9-64d80a97cc38","savedPayload":{"position":{"x":46274.467312818415,"y":31780.011584392756},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"434c3396-d53e-434b-aa8b-c27a170c9ed9","savedPayload":{"position":{"x":46203.31676678368,"y":31849.67669835976},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"9e784e59-be45-4895-9174-75d3dabfd45b","savedPayload":{"position":{"x":46121.67361107911,"y":31906.68977984249},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"3ad3e53b-53d4-4c61-a80d-e3f5a679b2cd","savedPayload":{"position":{"x":46028.33674024209,"y":31950.48529383432},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"5bfa0541-b9bb-4dc6-a5a0-5684278c2a16","savedPayload":{"position":{"x":45819.36840869368,"y":31385.419815840156},"targetPosition":{"x":45228.931388073186,"y":31502.74417203152},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":0,"y":80},"width":150,"height":190,"radius":0}}},"id":"63c36d69-c222-4687-a535-c147abcfb9a9","savedPayload":{"position":{"x":45229.38672237636,"y":31502.662848045977},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Volumes","rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"offset\": 0,\n \"minValue\": 3496.3969296811883,\n \"maxValue\": 12400.41819345618,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"6f07d76a-363a-4200-a7f4-ee9daf700744","savedPayload":{"position":{"x":43955.58254498447,"y":31504.999869023693},"targetPosition":{"x":43630.44688258736,"y":31530.628144529433},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"a6350c1f-64b3-4b13-921b-aea1eb09b6c1","savedPayload":{"position":{"x":44162.92610911858,"y":31723.43961416986},"targetPosition":{"x":43951.317213648734,"y":31685.17817589269},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Volumes","id":"4ec71a01-3d83-4e8e-9780-bcb700dcf66a"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"3b5badc4-8160-4c8f-ae19-8db7970e581d","savedPayload":{"position":{"x":44114.39338158986,"y":31825.20197219218},"targetPosition":{"x":43951.317213648734,"y":31685.17817589269},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Volume-Stairs","id":"dd1f89bc-3c2f-4fc4-a5f1-de7a34e0997e"}}}],"id":"929661b5-acce-457c-a8cc-505a64cfacf2","savedPayload":{"position":{"x":43951.317213648734,"y":31685.17817589269},"targetPosition":{"x":43630.44688258736,"y":31530.628144529433},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":150,"y":80},"width":150,"height":40,"radius":0}}},"id":"6f781018-392b-4ba2-b00f-68d4c5498f2b","savedPayload":{"position":{"x":45239.67954677261,"y":31659.506072974258},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"d4098b73-7e25-434e-8fd9-342393cbb400"}}},{"type":"Timeline Chart","name":"Oscilators","rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"minValue\": -2,\n \"maxValue\": 2,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"5074db98-f742-4771-a68e-a001b9c45182","savedPayload":{"position":{"x":43911.24885028547,"y":31610.221317602256},"targetPosition":{"x":43610.47458913771,"y":31590.810656481015},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"830c5295-25ca-4f08-bc7c-262be9e2fb17","savedPayload":{"position":{"x":44079.22692482198,"y":31836.109592766257},"targetPosition":{"x":43875.02284358463,"y":31767.22077825714},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Percentage Bandwidth","id":"9b5b1b81-c68c-45a5-8123-1a114a24b744"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"8aa6fde7-89cb-43af-bea1-149a0b94c29c","savedPayload":{"position":{"x":44017.101529873595,"y":31929.337889021248},"targetPosition":{"x":43875.02284358463,"y":31767.22077825714},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"RSI14","id":"71351f63-10b4-4f89-ac9f-a8380bb0b75e"}}}],"id":"2f754398-30ac-4afa-8c12-c37013b8a4da","savedPayload":{"position":{"x":43875.02284358463,"y":31767.22077825714},"targetPosition":{"x":43610.47458913771,"y":31590.810656481015},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":300,"y":80},"width":150,"height":40,"radius":0}}},"id":"805c6659-38e2-4959-86d0-cbabc87a8184","savedPayload":{"position":{"x":45208.98090305957,"y":31813.870110660417},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"MACD","rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"minValue\": -2,\n \"maxValue\": 2,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"53e5ea3b-83b8-4460-ac02-c68e2f17c85f","savedPayload":{"position":{"x":43840.620290316496,"y":31789.82237029658},"targetPosition":{"x":43558.71007204624,"y":31694.11687469478},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"775d50c1-df4b-4d02-9da4-de31dd5fc006","savedPayload":{"position":{"x":43963.892899882354,"y":31948.760513223307},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Percentage Bandwidth","id":"9b5b1b81-c68c-45a5-8123-1a114a24b744"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"06f133c7-2bf0-4352-89e7-bd43dc74c3e7","savedPayload":{"position":{"x":43920.721667853606,"y":32029.92468150603},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"RSI14","id":"71351f63-10b4-4f89-ac9f-a8380bb0b75e"}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"ae91005d-2615-4b4e-bcd4-3fb25ef7f510","savedPayload":{"position":{"x":43853.974112885124,"y":32090.426818984488},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"1939dd65-d12b-436d-8c76-609fc1f776f4","savedPayload":{"position":{"x":43757.20500332351,"y":32127.063274152384},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"68537c49-d84e-4843-9ee2-9aae3f9abef1","savedPayload":{"position":{"x":43767.25622614087,"y":31932.288004103884},"targetPosition":{"x":43558.71007204624,"y":31694.11687469478},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":300,"y":80},"width":150,"height":40,"radius":0}}},"id":"35419f31-46aa-455e-b9bc-1f87a04e7903","savedPayload":{"position":{"x":45139.353748382324,"y":31955.03611259359},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Live Trading","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{ \n\"value\": \"24-hs\"\n}","id":"1ce27f77-c38b-49b1-8ea3-c32687bdb655","savedPayload":{"position":{"x":43783.37261367022,"y":31886.571656235734},"targetPosition":{"x":43521.05702874397,"y":31745.958156152803},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"b672763b-52c9-47c0-8b03-8156a564a6bb","savedPayload":{"position":{"x":43886.41967462734,"y":32119.941637293174},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"247187f3-1aaf-4de1-a7db-c3a550cdbad0"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"e330fc5d-223f-4d13-8251-84be95894b04","savedPayload":{"position":{"x":43842.96035016497,"y":32187.313417315454},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"4deb0411-a8ea-465e-adcd-e0515f291a2d"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"63396270-7226-4fe7-8a77-072a29e73691","savedPayload":{"position":{"x":43780.15086984105,"y":32228.511805578368},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"cec4bf02-ebee-48ad-a0cc-25629e34fd65"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"d08e1626-0cdc-42b6-ab7e-ed35fc9f0f43","savedPayload":{"position":{"x":43677.873644697815,"y":32238.940731261504},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"c9cbb548-60ef-491d-b083-5825573db463"}}}],"id":"c9302ae8-6f99-41c2-86d7-04e6b1cf1817","savedPayload":{"position":{"x":43690.30875570328,"y":32020.727785295832},"targetPosition":{"x":43521.05702874397,"y":31745.958156152803},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1770,"y":80},"width":150,"height":40,"radius":0}}},"id":"02c55b2b-cb65-4131-aa82-de04ce1ca1c6","savedPayload":{"position":{"x":45035.54820924473,"y":32073.374045997527},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Forward Testing","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{ \n\"value\": \"24-hs\"\n}","id":"73a8ce0d-317a-4162-b961-8ff3df64c1c3","savedPayload":{"position":{"x":43684.53879623016,"y":31970.012630752735},"targetPosition":{"x":43425.560345834936,"y":31822.587842697758},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"37d4edc7-51fc-4c9b-93b9-4f0f0e795208","savedPayload":{"position":{"x":43670.419810252366,"y":32282.9624054417},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"456e8c88-ba2b-487d-aa2a-48f6c1144d22"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"0f0b5b97-3962-4eaf-861e-edd447bc4d61","savedPayload":{"position":{"x":43609.82505581555,"y":32335.475436908324},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"6abfb364-1883-488b-ac98-ffa234013e95"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"fcb15fbd-c988-4b80-add2-88c0d20d2f90","savedPayload":{"position":{"x":43537.9321061552,"y":32357.541018716125},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"cdba5840-e1d7-4909-9422-423299403f3c"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"ec034ac9-5e70-44aa-99c7-d0afd8d0f78c","savedPayload":{"position":{"x":43436.996393413065,"y":32338.36265829784},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"0dbb4a2c-c32d-4bd1-a410-09ce23613604"}}}],"id":"2bc6080e-b3b8-4240-946b-b1475b407bdc","savedPayload":{"position":{"x":43510.26407319966,"y":32132.59925389586},"targetPosition":{"x":43425.560345834936,"y":31822.587842697758},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1620,"y":80},"width":150,"height":40,"radius":0}}},"id":"de6ca021-6112-41ab-8a4e-5b24682c397e","savedPayload":{"position":{"x":44904.62701626791,"y":32160.8029978036},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Paper Trading","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{ \n\"value\": \"24-hs\"\n}","id":"10022324-b8b0-4d63-ac68-1a52fb1a3bc8","savedPayload":{"position":{"x":43457.36591905919,"y":32128.545321458452},"targetPosition":{"x":43313.35171232311,"y":31869.073818844914},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"89f07041-b4af-47e8-9282-06f889d1873f","savedPayload":{"position":{"x":43418.773208240214,"y":32379.61274434846},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"442bd8c3-1825-4415-84bd-e2f1148b3f92"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"3ca3b1c6-2f56-4a69-a5b1-c6461b339286","savedPayload":{"position":{"x":43345.89520538448,"y":32412.88315685549},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"8ab46f29-e225-42ee-a17e-297b2ebcfdbe"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"8306fbd3-4c33-4796-8a00-259702921276","savedPayload":{"position":{"x":43270.93153284485,"y":32413.314969240215},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"2f5a9ed9-80b6-412f-9d24-47f49653be9b"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"bb634599-045c-4475-a4ed-af6798660d58","savedPayload":{"position":{"x":43179.038096809825,"y":32367.059336454447},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"ddb30433-09f1-44b5-afdc-28a05f218c25"}}}],"id":"0a45ce5c-57f1-442b-b5cf-a86812267f3c","savedPayload":{"position":{"x":43307.45180126069,"y":32190.479627192908},"targetPosition":{"x":43313.35171232311,"y":31869.073818844914},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1470,"y":80},"width":150,"height":40,"radius":0}}},"id":"d5405af5-9184-4017-805d-a7f9f9256524","savedPayload":{"position":{"x":44755.4514495045,"y":32211.35287786325},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Backtesting","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{\"value\":\"01-hs\"}","id":"37fba5b0-3028-4687-bb7b-1162995b2fe2","savedPayload":{"position":{"x":43247.66539437884,"y":32171.10718387298},"targetPosition":{"x":43182.81404257353,"y":31880.527527777554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"068284a9-fd5b-4b68-9103-836637fdcb0b","savedPayload":{"position":{"x":43141.24685193997,"y":32394.974326897627},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"e7f2581f-1dfd-44eb-82d9-a9be799307e6"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"4cfdb7dd-af3d-444d-abb8-f04d77e73889","savedPayload":{"position":{"x":43061.65443685055,"y":32406.489847624485},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"24dfc84b-74ec-4d79-9e97-4eb17dd2ac1d"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"1ea11e8c-049c-4ff1-ae9a-0f27d4165127","savedPayload":{"position":{"x":42989.62406345691,"y":32385.45184645436},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"ce87f885-a79b-4675-9cfd-337daed079e5"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"996074fc-0efb-4bf8-8f16-95cbad2f5043","savedPayload":{"position":{"x":42914.59155513783,"y":32314.693016214136},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"40c22092-c1c9-4a45-893a-b7372833df84"}}}],"id":"c49c4542-bc88-44c2-b537-6f2d5b3b3a3a","savedPayload":{"position":{"x":43087.94547795279,"y":32181.92277542006},"targetPosition":{"x":43182.81404257353,"y":31880.527527777554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1320,"y":80},"width":150,"height":40,"radius":0}}},"id":"cfda3689-ce58-460a-81c3-4b99377e7184","savedPayload":{"position":{"x":44598.07907263305,"y":32221.458401450443},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a4a8b171-8b07-4895-8c25-cedd8fa9c957","savedPayload":{"position":{"x":44638.52989266892,"y":31620.261680135452},"targetPosition":{"x":44068.49117132832,"y":31426.826447713953},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"frame":{"position":{"x":-1305,"y":-15},"width":240,"height":112.375,"radius":0}}},"id":"e6469c64-26cb-4a30-951d-a68147cf1ba5","savedPayload":{"position":{"x":44068.869578561484,"y":31427.048075342944},"targetPosition":{"x":42931.70615561109,"y":31041.063169142482},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"c01a6962-8b0e-4b06-8705-8751660aabbf","savedPayload":{"position":{"x":43452.64880473069,"y":31341.65552813608},"targetPosition":{"x":42931.70615561109,"y":31041.063169142482},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Dashboard","name":"Super Action Generated","id":"64fa2a31-21ea-4b56-a625-dc4082876a89"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.timeMachines === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Time Machine.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Dashboard') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Dashboard.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.timeMachines)\n target.timeMachines.push(templateClone)\n setup(templateClone, target)\n\n return templateClone\n }\n}","id":"968fd18b-2a1b-41ca-9317-23f301607857","savedPayload":{"position":{"x":43383.721289869136,"y":31437.822140544642},"targetPosition":{"x":42931.70615561109,"y":31041.063169142482},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"41c01e94-6c5c-4b71-9c70-8d962c86fec0","savedPayload":{"position":{"x":42931.70615561109,"y":31041.063169142482},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"/*\nMaster Script\n*/\nfunction newNewMasterScriptInstance() {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction, masterScript) {\n /* Validations */\n if (superAction.payload.parentNode.type !== 'Market') {\n superAction.payload.uiObject.setErrorMessage('Super Action Parent Node needs to be of type Market.')\n return\n }\n if (superAction.payload.parentNode.baseAsset === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Node needs to have a child Market Base Asset.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Node needs to have a child Market Quoted Asset.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Base Asset Node needs to reference an Asset.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Quoted Asset Node needs to reference an Asset.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Asset Node needs to be a child of Exchange Assets.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset.payload.referenceParent.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Asset Node needs to be a child of Exchange Assets.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Assets needs to be a child of an Exchange Node.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset.payload.referenceParent.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Assets needs to be a child of an Exchange Node.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode.payload.parentNode.id !== superAction.payload.parentNode.quotedAsset.payload.referenceParent.payload.parentNode.payload.parentNode.id) {\n superAction.payload.uiObject.setErrorMessage('Assets must be from the same Exchange.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market needs to be a child of Exchange Markets.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Markets needs to be a child of an Exchange.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.id !== superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode.payload.parentNode.id) {\n superAction.payload.uiObject.setErrorMessage('Assets and Market must be defined at the same Exchange.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange needs to have a child Exchange Accounts.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts.length === 0) {\n superAction.payload.uiObject.setErrorMessage('Exchange Accounts to needs have a child User Account.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[0].userKeys === undefined) {\n superAction.payload.uiObject.setErrorMessage('User Account needs to have a child User Keys.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[0].userKeys.keys.length === 0) {\n superAction.payload.uiObject.setErrorMessage('User Keys needs to have a child Exchange Account Key.')\n return\n }\n\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n if (templateScript.javascriptCode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Javascript Code.')\n return\n }\n if (templateScript.templateStructure === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Template Structure.')\n return\n }\n if (templateScript.templateTarget === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Template Target.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent.')\n return\n }\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n executionResult = templateScriptInstance.runValidations(superAction)\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n return true\n }\n\n function runScript(superAction, masterScript) {\n let market = superAction.payload.parentNode\n let exchange = superAction.payload.parentNode.payload.parentNode.payload.parentNode\n let key = exchange.exchangeAccounts.userAccounts[0].userKeys.keys[0]\n let singleMarketData\n let backSessionReference\n let paperSessionReference\n let liveSessionReference\n let ForwardSessionReference\n let dataMiningTaskManagers = []\n let testingEnvironmentTaskManager\n let productionEnvironmentTaskManager\n let timeMachine\n\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateClone\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n templateClone = templateScriptInstance.runScript(superAction, templateScript)\n }\n\n if (executionResult !== true) { return false }\n\n switch (templateClone.payload.parentNode.payload.parentNode.type) {\n case 'Session Independent Data': {\n singleMarketData = templateClone\n break\n }\n case 'Session Based Data': {\n switch (templateClone.templateType) {\n case 'Backtesting Session': {\n backSessionReference = templateClone\n break\n }\n case 'Paper Trading Session': {\n paperSessionReference = templateClone\n break\n }\n case 'Live Trading Session': {\n liveSessionReference = templateClone\n break\n }\n case 'Forward Testing Session': {\n ForwardSessionReference = templateClone\n break\n }\n }\n break\n }\n case 'Data Mining': {\n dataMiningTaskManagers.push(templateClone)\n break\n }\n case 'Testing Environment': {\n testingEnvironmentTaskManager = templateClone\n break\n }\n case 'Production Environment': {\n productionEnvironmentTaskManager = templateClone\n break\n }\n case 'Charting Space': {\n timeMachine = templateClone\n break\n }\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n /* Here we will find the sessions within the tasks in order to put them as a reference of the Session Reference nodes we already have */\n let session\n session = findSession('Backtesting Session', testingEnvironmentTaskManager)\n backSessionReference.payload.referenceParent = session\n session = findSession('Paper Trading Session', testingEnvironmentTaskManager)\n paperSessionReference.payload.referenceParent = session\n session = findSession('Live Trading Session', productionEnvironmentTaskManager)\n liveSessionReference.payload.referenceParent = session\n session = findSession('Forward Testing Session', productionEnvironmentTaskManager)\n ForwardSessionReference.payload.referenceParent = session\n\n function findSession(sessionType, taskManager) {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.session !== undefined) {\n if (process.session.type === sessionType) {\n return process.session\n }\n }\n }\n }\n }\n }\n }\n\n /* Here, we are going to connect the layers of the charts at the Time Machine */\n for (let i = 0; i < timeMachine.timelineCharts.length; i++) {\n let timelineChart = timeMachine.timelineCharts[i]\n switch (timelineChart.name) {\n case 'Indicators': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'Candles')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Bands')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 2: {\n let dataProduct = findDataProduct(singleMarketData, 'Resistances')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 3: {\n let dataProduct = findDataProduct(singleMarketData, 'Supports')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 4: {\n let dataProduct = findDataProduct(singleMarketData, 'Candles Channels')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 5: {\n let dataProduct = findDataProduct(singleMarketData, 'Candles Channels Objects')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 6: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Channels')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 7: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Channels Objects')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 8: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Sub-Channels')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 9: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Sub-Channels Objects')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 10: {\n let dataProduct = findDataProduct(singleMarketData, 'Popular SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 11: {\n let dataProduct = findDataProduct(singleMarketData, 'Base5 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 12: {\n let dataProduct = findDataProduct(singleMarketData, 'Base6 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 13: {\n let dataProduct = findDataProduct(singleMarketData, 'Base7 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 14: {\n let dataProduct = findDataProduct(singleMarketData, 'Base11 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 15: {\n let dataProduct = findDataProduct(singleMarketData, 'Popular EMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 16: {\n let dataProduct = findDataProduct(singleMarketData, 'Base7 EMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 17: {\n let dataProduct = findDataProduct(singleMarketData, 'Base11 EMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'Volumes': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'Volumes')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'Volume-Stairs')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'Oscilators': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'Percentage Bandwidth')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'RSI14')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'MACD': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 122609')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 031016')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 2: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 081709')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 3: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 245209')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'Backtesting': {\n setupSessionReference(timelineChart, backSessionReference)\n break\n }\n case 'Paper Trading': {\n setupSessionReference(timelineChart, paperSessionReference)\n break\n }\n case 'Live Trading': {\n setupSessionReference(timelineChart, liveSessionReference)\n break\n }\n case 'Forward Testing': {\n setupSessionReference(timelineChart, ForwardSessionReference)\n break\n }\n }\n }\n\n function findDataProduct(singleMarketData, name) {\n for (let i = 0; i < singleMarketData.dataProducts.length; i++) {\n let dataProduct = singleMarketData.dataProducts[i]\n if (dataProduct.name === name) {\n return dataProduct\n }\n }\n }\n function setupSessionReference(timelineChart, sessionReference) {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Simulation')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Conditions')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 2: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Trades')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 3: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Strategies')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n }\n\n let exchangeAndMarket = superAction.payload.parentNode.payload.parentNode.payload.parentNode.name + ' ' + market.name\n timeMachine.name = exchangeAndMarket\n\n for (let i = 0; i < dataMiningTaskManagers.length; i++) {\n let dataMiningTaskManager = dataMiningTaskManagers[i]\n dataMiningTaskManager.name = dataMiningTaskManager.name + ' ' + market.name\n }\n\n testingEnvironmentTaskManager.name = testingEnvironmentTaskManager.name + ' ' + market.name\n productionEnvironmentTaskManager.name = productionEnvironmentTaskManager.name + ' ' + market.name\n return true\n }\n}\n/*\nEnd Manster Script\n*/","id":"d234c201-4b50-48c6-be9d-f3b141c8cc16","savedPayload":{"position":{"x":42315.379156118935,"y":30864.883508780593},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"95b24e48-3b10-47cb-b149-8667571f4a83","savedPayload":{"position":{"x":41890.236139228655,"y":30439.426551049364},"targetPosition":{"x":40847.785430914875,"y":31041.241941751345},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"o"}}},{"type":"Master Script","name":"Delete Market Dependencies ","templateScripts":[{"type":"Template Script","name":"Delete Testing Environment Task Managers","templateTarget":{"type":"Template Target","name":"New Template Target","id":"a97aa286-3199-4362-900a-65b604bf5b9a","savedPayload":{"position":{"x":41729.477786552314,"y":31046.11236071952},"targetPosition":{"x":41582.54274899229,"y":31205.193628384397},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Testing Environment","name":"New Testing Environment","id":"2ae685a1-25c6-4bd5-b559-d83e373e8d45"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Testing Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeTasks.length; e++) {\n let exchangeTasks = target.exchangeTasks[e]\n if (exchangeTasks.payload.referenceParent !== undefined) {\n if (exchangeTasks.payload.referenceParent.id === exchange.id) {\n for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {\n let taskManager = exchangeTasks.taskManagers[k]\n checkReferenceToMarket()\n function checkReferenceToMarket() {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n if (process.marketReference.payload !== undefined) {\n if (process.marketReference.payload.referenceParent !== undefined) {\n if (process.marketReference.payload.referenceParent.id === market.id) {\n nodesToDelete.push(taskManager)\n return\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n if (exchangeTasks.taskManagers.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeTasks)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"9d020e32-68e5-4123-b0ad-cb443d778905","savedPayload":{"position":{"x":41785.524618173,"y":31129.822745374793},"targetPosition":{"x":41582.54274899229,"y":31205.193628384397},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"12eb790b-7f24-48bf-a4f0-34159f3d1863","savedPayload":{"position":{"x":41582.54274899229,"y":31205.193628384397},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Production Environment Task Managers","templateTarget":{"type":"Template Target","name":"New Template Target","id":"2ef5ba0f-10f7-4af0-81c1-2e89b9aea583","savedPayload":{"position":{"x":41853.66308982842,"y":31264.801386694602},"targetPosition":{"x":41656.82837164478,"y":31355.520360853872},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Production Environment","name":"New Production Environment","id":"32656457-f603-416a-b805-546642094dc4"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Production Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeTasks.length; e++) {\n let exchangeTasks = target.exchangeTasks[e]\n if (exchangeTasks.payload.referenceParent !== undefined) {\n if (exchangeTasks.payload.referenceParent.id === exchange.id) {\n for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {\n let taskManager = exchangeTasks.taskManagers[k]\n checkReferenceToMarket()\n function checkReferenceToMarket() {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n if (process.marketReference.payload !== undefined) {\n if (process.marketReference.payload.referenceParent !== undefined) {\n if (process.marketReference.payload.referenceParent.id === market.id) {\n nodesToDelete.push(taskManager)\n return\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n if (exchangeTasks.taskManagers.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeTasks)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"9f9515a8-2e47-4a6f-acc3-6e4e01a610b2","savedPayload":{"position":{"x":41873.37022951505,"y":31363.719899709926},"targetPosition":{"x":41656.82837164478,"y":31355.520360853872},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"29b330a4-d4f6-45ac-8b41-090740dc418f","savedPayload":{"position":{"x":41656.82837164478,"y":31355.520360853872},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Data Mining Task Managers","templateTarget":{"type":"Template Target","name":"New Template Target","id":"c3417c9a-68b3-4d49-9514-0d2d30cea254","savedPayload":{"position":{"x":41884.48440596237,"y":31513.967037417144},"targetPosition":{"x":41667.84415563366,"y":31522.410064016763},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Mining","name":"New Data Mining","id":"7449a608-32ca-4285-94d1-c96791e2817f"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Data Mining') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeTasks.length; e++) {\n let exchangeTasks = target.exchangeTasks[e]\n if (exchangeTasks.payload.referenceParent !== undefined) {\n if (exchangeTasks.payload.referenceParent.id === exchange.id) {\n for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {\n let taskManager = exchangeTasks.taskManagers[k]\n checkReferenceToMarket()\n function checkReferenceToMarket() {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n if (process.marketReference.payload !== undefined) {\n if (process.marketReference.payload.referenceParent !== undefined) {\n if (process.marketReference.payload.referenceParent.id === market.id) {\n nodesToDelete.push(taskManager)\n return\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n if (exchangeTasks.taskManagers.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeTasks)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"2d8e00e3-836a-426d-9f78-db3262bd7454","savedPayload":{"position":{"x":41864.71847634008,"y":31612.922947914863},"targetPosition":{"x":41667.84415563366,"y":31522.410064016763},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"e8e96f57-041e-4c0d-a2bf-5a8e444636b1","savedPayload":{"position":{"x":41667.84415563366,"y":31522.410064016763},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Dashboard Time Machines","templateTarget":{"type":"Template Target","name":"New Template Target","id":"1cbc9a0e-d00e-4506-9a33-5fbcb3ed8900","savedPayload":{"position":{"x":41818.05356628126,"y":31755.042846552373},"targetPosition":{"x":41614.02097354356,"y":31680.97605960732},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Dashboard","name":"Super Action Generated","id":"64fa2a31-21ea-4b56-a625-dc4082876a89"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Dashboard') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Dashboard.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let k = 0; k < target.timeMachines.length; k++) {\n let timeMachine = target.timeMachines[k]\n for (let i = 0; i < timeMachine.timelineCharts.length; i++) {\n let timelineChart = timeMachine.timelineCharts[i]\n if (timelineChart.layersManager !== undefined) {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n if (layer.payload.referenceParent !== undefined) {\n if (layer.payload.referenceParent.payload !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode.payload !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode.payload.referenceParent !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode.payload.referenceParent.id === market.id) {\n \n nodesToDelete.push(timeMachine)\n \n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"cf8f6b69-70f8-4035-801d-276c1e2d6b5d","savedPayload":{"position":{"x":41760.499195398515,"y":31841.017373526123},"targetPosition":{"x":41614.02097354356,"y":31680.97605960732},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"c78e76fc-311b-4ac7-942f-af9629ea8b76","savedPayload":{"position":{"x":41614.02097354356,"y":31680.97605960732},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Single Market Data","templateTarget":{"type":"Template Target","name":"New Template Target","id":"2d36e591-3314-4a0c-b04d-6372e961959d","savedPayload":{"position":{"x":41662.60051045396,"y":31954.075050489493},"targetPosition":{"x":41503.55623477179,"y":31806.85723421229},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Independent Data","name":"New Session Independent Data","id":"bf04ccea-266d-4115-b50e-fefdf29b873a"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Independent Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Independent Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeDataProducts.length; e++) {\n let exchangeDataProducts = target.exchangeDataProducts[e]\n if (exchangeDataProducts.payload.referenceParent !== undefined) {\n if (exchangeDataProducts.payload.referenceParent.id === exchange.id) {\n for (let i = 0; i < exchangeDataProducts.singleMarketData.length; i++) {\n let singleMarketData = exchangeDataProducts.singleMarketData[i]\n if (singleMarketData.payload !== undefined) {\n if (singleMarketData.payload.referenceParent !== undefined) {\n if (singleMarketData.payload.referenceParent.id === market.id) {\n nodesToDelete.push(singleMarketData)\n }\n }\n }\n }\n if (exchangeDataProducts.singleMarketData.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeDataProducts)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"168466d3-1446-4b45-a647-ad962d1d5f2c","savedPayload":{"position":{"x":41578.48746317018,"y":32010.157336419907},"targetPosition":{"x":41503.55623477179,"y":31806.85723421229},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"f23edd8e-b0f9-4531-9550-69cdb0616bcc","savedPayload":{"position":{"x":41503.55623477179,"y":31806.85723421229},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Session References","templateTarget":{"type":"Template Target","name":"New Template Target","id":"4fe33613-3b67-4e0f-a4b4-6caa4e6dd163","savedPayload":{"position":{"x":41443.711470549744,"y":32077.739391028543},"targetPosition":{"x":41353.18735100165,"y":31880.891142726974},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeSessions.length; e++) {\n let exchangeSessions = target.exchangeSessions[e]\n if (exchangeSessions.payload.referenceParent !== undefined) {\n if (exchangeSessions.payload.referenceParent.id === exchange.id) {\n for (let i = 0; i < exchangeSessions.sessionReferences.length; i++) {\n let sessionReference = exchangeSessions.sessionReferences[i]\n let singleMarketData = sessionReference.singleMarketData\n if (singleMarketData !== undefined) {\n if (singleMarketData.payload !== undefined) {\n if (singleMarketData.payload.referenceParent !== undefined) {\n if (singleMarketData.payload.referenceParent.id === market.id) {\n nodesToDelete.push(sessionReference)\n }\n }\n }\n }\n }\n if (exchangeSessions.sessionReferences.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeSessions)\n }\n }\n }\n }\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"f2cc52fd-cea8-437f-aa7d-a4d1100bb92e","savedPayload":{"position":{"x":41344.40711731363,"y":32097.37996261368},"targetPosition":{"x":41353.18735100165,"y":31880.891142726974},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"f8ffadd5-8eda-4c4e-be1c-c49b98045b42","savedPayload":{"position":{"x":41353.18735100165,"y":31880.891142726974},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"/*\nMaster Script\n*/\nfunction newNewMasterScriptInstance() {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction, masterScript) {\n /* Validations */\n if (superAction.payload.parentNode.type !== 'Market') {\n superAction.payload.uiObject.setErrorMessage('Super Action Parent Node needs to be of type Market.')\n return\n }\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n if (superAction.payload.parentNode.type !== 'Market') {\n superAction.payload.uiObject.setErrorMessage('Super Action Parent Node needs to be of type Market.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market needs to be a child of Exchange Markets.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Markets needs to be a child of an Exchange.')\n return\n }\n if (templateScript.javascriptCode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Javascript Code.')\n return\n }\n if (templateScript.templateTarget === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Template Target.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent.')\n return\n }\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n executionResult = templateScriptInstance.runValidations(superAction)\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n return true\n }\n\n function runScript(superAction, masterScript) {\n let market = superAction.payload.parentNode\n let exchange = superAction.payload.parentNode.payload.parentNode.payload.parentNode\n\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateClone\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n templateClone = templateScriptInstance.runScript(superAction, templateScript)\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n return true\n }\n}\n/*\nEnd Manster Script\n*/","id":"a9538a7f-32dd-4068-8af6-0f8a83f6f3c7","savedPayload":{"position":{"x":41186.32467320293,"y":31891.49809390885},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"c25d0331-8ac0-4834-aa77-743487455d45","savedPayload":{"position":{"x":41890.23583929248,"y":31643.061431023485},"targetPosition":{"x":40847.785430914875,"y":31041.241941751345},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1dd2a75d-7d97-4994-8c95-b392d933b1c1","savedPayload":{"position":{"x":40847.785430914875,"y":31041.241941751345},"targetPosition":{"x":39643.041459281856,"y":31041.13836154292},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"S"}}},"id":"17b5ea06-62d8-4a79-b0ba-f9d3b997c17f","savedPayload":{"position":{"x":39643.041459281856,"y":31041.13836154292},"targetPosition":{"x":39643.041459281856,"y":31041.13836154292},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}} \ No newline at end of file +{"type":"Super Scripts","name":"Masters","code":"{ \n\"codeName\": \"Masters\"\n}","scriptsLibrary":{"type":"Scripts Library","name":"New Scripts Library","masterScripts":[{"type":"Master Script","name":"Setup Market","templateScripts":[{"type":"Template Script","name":"Install Single Market Data","templateStructure":{"type":"Template Structure","name":"New Template Structure","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Candles","id":"24c5f795-acf3-43c3-99ee-ddb521b082f4","savedPayload":{"position":{"x":39655.39345377463,"y":28084.121285348076},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles","id":"fe6065ec-4d2d-47a0-bae0-8647f0e195d7"}}},{"type":"Data Product","name":"Volumes","id":"204ec959-0861-486a-aa2a-276dbf48f775","savedPayload":{"position":{"x":39619.08803001109,"y":27971.34006849996},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Volumes","id":"1a366aef-efef-470e-88e0-2a6fbe4dd91f"}}},{"type":"Data Product","name":"Candles Channels","id":"90ee9733-ab7e-41a0-ac78-4a7661a27017","savedPayload":{"position":{"x":39594.11393185164,"y":27855.840877332517},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles Channels","id":"8c81d378-0947-4e9d-be25-58e69120152e"}}},{"type":"Data Product","name":"Candles Channels Objects","id":"9ade8e93-9071-422b-8cce-7a50d6dad739","savedPayload":{"position":{"x":39580.59887306321,"y":27738.507421368748},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles Channels Objects","id":"ba5dbb67-2030-42a0-9ff8-1ee80874f047"}}},{"type":"Data Product","name":"Bollinger Bands","id":"b5ef2cf7-3d35-45ec-848c-31970b59c04e","savedPayload":{"position":{"x":39578.655464896365,"y":27620.63069875915},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Bands","id":"29088877-8d63-4217-8d34-8742afc262f4"}}},{"type":"Data Product","name":"Percentage Bandwidth","id":"1c3881ca-24b3-4cc9-b16b-9db7aa2b4c24","savedPayload":{"position":{"x":39588.297198778826,"y":27502.946480135615},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Percentage Bandwidth","id":"3b7885a0-cd72-4a3f-a38b-3dc8c47bd068"}}},{"type":"Data Product","name":"Bollinger Channels Objects","id":"92eec33b-86be-4b73-899f-e9550f7b0df3","savedPayload":{"position":{"x":39609.428887621216,"y":27386.77785099828},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Channels Objects","id":"1f111f72-624f-4166-9f99-ab8d785d5267"}}},{"type":"Data Product","name":"Bollinger Sub-Channels Objects","id":"4b69df71-9091-4d22-9b0d-571304297033","savedPayload":{"position":{"x":39641.84608404141,"y":27273.24280350331},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Sub-Channels Objects","id":"080c87fc-4546-4482-ab11-180a33f6d9ab"}}},{"type":"Data Product","name":"Popular SMAs","id":"8a5f7e27-9ca5-4d6e-b6b9-2d6cbdda8040","savedPayload":{"position":{"x":39685.2360649887,"y":27163.433854438405},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Popular SMAs","id":"4029d5bf-a8ad-4d80-b874-2af506e0ab34"}}},{"type":"Data Product","name":"Base7 SMAs","id":"ccd9c3fd-3382-4fea-8798-a29b04efc09e","savedPayload":{"position":{"x":39739.18066448831,"y":27058.407968059906},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base7 SMAs","id":"a03ef0ea-5be9-4d40-952e-24789e77dbb6"}}},{"type":"Data Product","name":"Base11 SMAs","id":"aebc2010-a6e2-436d-8cc6-96eb0689330d","savedPayload":{"position":{"x":39803.16019594351,"y":26959.1762369918},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base11 SMAs","id":"05fabbf9-7798-4a53-995e-53ae74bef3de"}}},{"type":"Data Product","name":"Popular EMAs","id":"0fd4ef59-77b4-48cc-b00e-6a529a05cb0c","savedPayload":{"position":{"x":39876.55840037216,"y":26866.694054096202},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Popular EMAs","id":"d5894707-b065-429b-ba6b-78c71e6529fe"}}},{"type":"Data Product","name":"Base7 EMAs","id":"802917a7-816b-4e4c-bbb6-a0eaf9eb7429","savedPayload":{"position":{"x":39958.66834477317,"y":26781.851861425766},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base7 EMAs","id":"b42348a4-6a26-4d46-aa2e-cbf5fa0a4984"}}},{"type":"Data Product","name":"Base11 EMAs","id":"9109bfcb-70e7-4cf8-9047-da9567668082","savedPayload":{"position":{"x":40048.69920292061,"y":26705.466552453436},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base11 EMAs","id":"21df4f44-96eb-453b-8652-4ce2dc03424c"}}},{"type":"Data Product","name":"MACD 122609","id":"519a89d4-75bb-4c33-a63d-d65c9defbd12","savedPayload":{"position":{"x":40145.78385240617,"y":26638.273605375165},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 122609","id":"6c0c9bf0-90af-4497-b4fb-49b5872b1119"}}},{"type":"Data Product","name":"RSI14","id":"6ae3a0ab-86cf-4e81-b415-5775dfcb1d9c","savedPayload":{"position":{"x":40248.98721974834,"y":26580.920017944973},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"RSI14","id":"e24248b9-90df-4b26-b5cc-0bac80efa0c7"}}},{"type":"Data Product","name":"Base6 SMAs","id":"f4d7169e-8c26-48ae-967d-529a8ecab1f1","savedPayload":{"position":{"x":40357.31529765827,"y":26533.95809992029},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base6 SMAs","id":"4ae22e54-cd02-4e2e-b638-1c5ea9a171ac"}}},{"type":"Data Product","name":"Base5 SMAs","id":"bae79086-c788-4b79-a9af-952326480b34","savedPayload":{"position":{"x":40469.72474382882,"y":26497.840166222424},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Base5 SMAs","id":"2dcb55de-1673-409e-aa52-f51e7c7f8fa3"}}},{"type":"Data Product","name":"Bollinger Channels","id":"b4847c45-613e-4862-98d3-34b28fa70fd7","savedPayload":{"position":{"x":40585.132955318084,"y":26472.914171771416},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Channels","id":"94bc3958-cc85-43a1-a178-103e84f42910"}}},{"type":"Data Product","name":"Bollinger Sub-Channels","id":"05766faf-47e0-493a-9459-a470a6691138","savedPayload":{"position":{"x":40702.42850753783,"y":26459.42033593334},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Sub-Channels","id":"79ce9c05-8a4c-4b58-850f-288037acc702"}}},{"type":"Data Product","name":"MACD 122609","id":"c5d7a5f6-2924-46a6-8d04-1100a5088da9","savedPayload":{"position":{"x":40820.28557415008,"y":26457.488926076465},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 122609","id":"6c0c9bf0-90af-4497-b4fb-49b5872b1119"}}},{"type":"Data Product","name":"MACD 031016","id":"db37547e-f473-4dca-9bae-60fb37c724a5","savedPayload":{"position":{"x":40937.95845139182,"y":26467.13880986452},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 031016","id":"d1482dc0-e9f1-47ba-b75e-961fa914a53a"}}},{"type":"Data Product","name":"MACD 081709","id":"0ec41f8a-6170-439b-9513-7c4b2a835c6d","savedPayload":{"position":{"x":41054.120384899,"y":26488.277064496815},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 081709","id":"683e6b29-9f82-4a8e-93eb-20ac0e81a750"}}},{"type":"Data Product","name":"MACD 245209","id":"03d654c7-5641-4408-bf15-54ee7203d80b","savedPayload":{"position":{"x":41167.65178678811,"y":26520.700545782674},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"MACD 245209","id":"47984765-8180-45cf-a9eb-f0a8b22d165d"}}},{"type":"Data Product","name":"Candles","id":"7b7c2f35-514c-4ad4-bdc5-7cb462020ac1","savedPayload":{"position":{"x":41277.4594798642,"y":26564.097566374792},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Candles","id":"2443883b-6959-42a3-ae3b-ef3102ffcdcc"}}},{"type":"Data Product","name":"Volumes","id":"4b1fcdaf-3bf1-4bdb-973d-42facd0b7104","savedPayload":{"position":{"x":41382.48641024106,"y":26618.051155654128},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Volumes","id":"3a43c6f3-bac0-404e-865f-822eb48c8660"}}},{"type":"Data Product","name":"Bollinger Channels","id":"6f3bbf18-00d5-4f5a-9c48-a7e5e73e72e2","savedPayload":{"position":{"x":41481.72199276818,"y":26682.043494609432},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Channels","id":"94bc3958-cc85-43a1-a178-103e84f42910"}}},{"type":"Data Product","name":"Bollinger Sub-Channels","id":"bbe5af35-1f9e-4a31-9b5a-47ee5999e97c","savedPayload":{"position":{"x":41574.21247985582,"y":26755.461785010735},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Bollinger Sub-Channels","id":"79ce9c05-8a4c-4b58-850f-288037acc702"}}},{"type":"Data Product","name":"External Signals","id":"4e208ac6-227a-4c6d-b548-df9a06a96f2a","savedPayload":{"position":{"x":41659.072595416554,"y":26837.602086697607},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"External Signals","id":"29196db8-4c41-47ea-be7e-952cd9be9a0b"}}},{"type":"Data Product","name":"Resistances","id":"cb998af9-8b76-4163-ba59-a354e85e625b","savedPayload":{"position":{"x":41734.78246123321,"y":26926.742016325188},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Resistances","id":"7a734501-a0fb-4305-803e-7e95d477c513"}}},{"type":"Data Product","name":"Supports","id":"935f01dc-bbe2-4931-a0d9-19f820cf04c1","savedPayload":{"position":{"x":41803.59685318991,"y":27026.194745818015},"targetPosition":{"x":40780.996436099675,"y":27659.843516028},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Supports","id":"bc3c43de-6312-47d4-ae3d-0c95c1b039e0"}}}],"id":"3c9e658e-9350-4b43-b95c-1dc9fbb7de7b","savedPayload":{"position":{"x":40780.522571395006,"y":27659.757222847853},"targetPosition":{"x":41312.42037870206,"y":28738.475444229476},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":5,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"922126c5-3b95-4c76-90ce-c85435c49624","savedPayload":{"position":{"x":41312.452880728124,"y":28738.45103113137},"targetPosition":{"x":41578.71473951283,"y":29277.75973906997},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"79c82a1e-4c4e-4622-b762-4ccbfdac46b7","savedPayload":{"position":{"x":41423.01737185227,"y":28696.77991254939},"targetPosition":{"x":41578.71473951283,"y":29277.75973906997},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Independent Data","name":"New Session Independent Data","id":"bf04ccea-266d-4115-b50e-fefdf29b873a"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.singleMarketData === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Single Market Data.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Independent Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Independent Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.singleMarketData)\n setup(templateClone, target, exchange, 'Exchange Data Products', 'singleMarketData')\n templateClone.payload.referenceParent = market\n\n return templateClone\n }\n}","id":"255fc8f9-413a-4334-b6f5-d944d57fb27c","savedPayload":{"position":{"x":41539.572203582546,"y":28677.555510830884},"targetPosition":{"x":41578.71473951283,"y":29277.75973906997},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"4dd2897c-d7e2-4fa2-a92f-4d0cf49a6370","savedPayload":{"position":{"x":41578.71473951283,"y":29277.75973906997},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Back WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"169aa57f-d46b-4979-b8a2-df6ba21eabc6","savedPayload":{"position":{"x":40971.62547458223,"y":28704.67815778764},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"41351410-0495-4419-8a78-babed3a2f25c","savedPayload":{"position":{"x":40739.2805936536,"y":28612.68448755448},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"fc43d350-5a2e-4948-8279-6444a15b39ed","savedPayload":{"position":{"x":40648.47472250254,"y":28380.33960662585},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"8f8c543a-5add-460f-9767-06cc069e142b","savedPayload":{"position":{"x":40749.374350272024,"y":28158.088482315645},"targetPosition":{"x":40971.62547458223,"y":28380.33960662585},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"0497555c-1024-4c2c-8be4-9c8917bed11f","savedPayload":{"position":{"x":40971.62547458223,"y":28380.33960662585},"targetPosition":{"x":41034.51136563778,"y":28696.544935681453},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"5d4fb545-1392-4651-8f54-32b3b0b2278f","savedPayload":{"position":{"x":41035.68589463498,"y":28694.595676926525},"targetPosition":{"x":41161.48169175592,"y":29327.013855736088},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"726cd3f8-cfce-4e06-8e13-f4b2523e6e57","savedPayload":{"position":{"x":41772.65093280171,"y":28646.807955048214},"targetPosition":{"x":41890.128901652744,"y":29236.71585692822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"d5bc03d5-baeb-4ab0-91a6-cc61d0122474","savedPayload":{"position":{"x":41890.12930892233,"y":28635.18613310936},"targetPosition":{"x":41890.128901652744,"y":29236.71585692822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Backtesting Session'\n return templateClone\n }\n}","id":"5201c9bb-45d3-4066-970d-56d13eb6905e","savedPayload":{"position":{"x":42007.53909316859,"y":28646.791845625325},"targetPosition":{"x":41890.128901652744,"y":29236.71585692822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"caa968ef-9a38-4fb4-998f-cb7e27a3eedd","savedPayload":{"position":{"x":41890.128901652744,"y":29236.71585692822},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Paper WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"fe43a58c-8d97-4f98-ba33-6328c1bcb7a7","savedPayload":{"position":{"x":41557.474267378886,"y":28704.538380895512},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"447096dc-d9b3-472d-985e-7810458651d2","savedPayload":{"position":{"x":41328.36437304013,"y":28614.669001956987},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"87f61b2b-cd47-42db-b8a8-2d2b84c30712","savedPayload":{"position":{"x":41238.701960314706,"y":28385.55910761823},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"fe0ed876-65d6-488c-8572-86708cd2f4b8","savedPayload":{"position":{"x":41328.657032916315,"y":28156.74187315566},"targetPosition":{"x":41557.474267378886,"y":28385.55910761823},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"4844333d-4238-412b-b2f3-403d78d1aaa8","savedPayload":{"position":{"x":41557.474267378886,"y":28385.55910761823},"targetPosition":{"x":41536.37709492533,"y":28707.266395142844},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"9bf71a35-9ee8-4679-89c7-e9894c3963cd","savedPayload":{"position":{"x":41538.15110500089,"y":28705.54204056541},"targetPosition":{"x":41495.97864513366,"y":29348.96944058102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f7b51fbd-c90d-4b5a-9242-9de2a40f4733","savedPayload":{"position":{"x":42240.40982433613,"y":28677.441521776378},"targetPosition":{"x":42201.34918584835,"y":29277.69269398605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"b7adb316-4430-4933-80ef-c8e239a947fc","savedPayload":{"position":{"x":42356.951494870984,"y":28696.641898991125},"targetPosition":{"x":42201.34918584835,"y":29277.69269398605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Paper Trading Session'\n return templateClone\n }\n}","id":"09fd8aa3-006f-4c13-8084-78da7f7ed4ca","savedPayload":{"position":{"x":42467.48260549182,"y":28738.277097634553},"targetPosition":{"x":42201.34918584835,"y":29277.69269398605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"ce8531c2-42df-4c54-b4ea-48d7ca9861e5","savedPayload":{"position":{"x":42201.34918584835,"y":29277.69269398605},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Live WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"22bcc102-5751-444e-a609-bf050743c987","savedPayload":{"position":{"x":42124.2954678251,"y":28863.166820214137},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"a97553e6-85bd-4f7d-b7a3-6235e540e1a8","savedPayload":{"position":{"x":41899.82203310804,"y":28765.34236295704},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"6adfe7ca-0353-4506-a1ac-09bd6b6a7c86","savedPayload":{"position":{"x":41803.41381185934,"y":28540.86892823998},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"9136e76e-e3e4-48c6-a6bd-e1a64176c7a3","savedPayload":{"position":{"x":41897.44019113757,"y":28314.01365155245},"targetPosition":{"x":42124.2954678251,"y":28540.86892823998},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"f267f52b-25be-4822-90b7-88bf3121d009","savedPayload":{"position":{"x":42124.2954678251,"y":28540.86892823998},"targetPosition":{"x":42020.66210112073,"y":28846.1629818523},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"a095996a-c30e-45e8-8598-7b6010b2095b","savedPayload":{"position":{"x":42020.66210112073,"y":28846.1629818523},"targetPosition":{"x":41813.395367712,"y":29456.751089076944},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"9df1f12f-0030-4c41-98e2-d07482ff8d22","savedPayload":{"position":{"x":42684.61234689323,"y":28828.20083898273},"targetPosition":{"x":42491.49072073263,"y":29397.8621330702},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"b5554d40-5014-45ec-b996-e74e05c5e872","savedPayload":{"position":{"x":42792.21171122186,"y":28876.91139330717},"targetPosition":{"x":42491.49072073263,"y":29397.8621330702},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Live Trading Session'\n return templateClone\n }\n}","id":"ade4b7d5-1066-43fa-b6af-e2d1213c4d14","savedPayload":{"position":{"x":42888.19927444742,"y":28945.736615872695},"targetPosition":{"x":42491.49072073263,"y":29397.8621330702},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"3e9a2343-49bb-43d5-adda-0072b01313f1","savedPayload":{"position":{"x":42491.49072073263,"y":29397.8621330702},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Forward WHB Session Reference","templateStructure":{"type":"Template Structure","name":"New Template Structure","sessionReferences":{"type":"Session Reference","name":" ","singleMarketData":{"type":"Single Market Data","name":"","dataProducts":[{"type":"Data Product","name":"Simulation","id":"b03fa95d-fb4d-4cef-9590-70bcf0034123","savedPayload":{"position":{"x":42492.63963403888,"y":28500.7397102676},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Simulation","id":"a3201051-46ba-4a92-aef3-a6c787b771fd"}}},{"type":"Data Product","name":"Conditions","id":"352b1a8a-1d74-44ff-b9f3-411f337db931","savedPayload":{"position":{"x":42710.36396962775,"y":28492.07047492605},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Conditions","id":"7fb3e69f-4343-47f8-ab7b-6fcf8a805fc0"}}},{"type":"Data Product","name":"Strategies","id":"c7ed2f21-cd86-44fe-a946-1dc00e5f8fb3","savedPayload":{"position":{"x":42891.7169300037,"y":28613.377753949273},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Strategies","id":"36051f0f-5161-48a6-9d13-eb1766b5566b"}}},{"type":"Data Product","name":"Trades","id":"13d4f731-4ee0-4a24-810a-f4760432e33e","savedPayload":{"position":{"x":42967.139262708275,"y":28818.147267745622},"targetPosition":{"x":42614.716102802915,"y":28831.67823199434},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Product Definition","name":"Trades","id":"a26dc922-ca08-43ef-983e-029b76f39163"}}}],"id":"a70a92df-4abe-421d-92b2-ef70a756441c","savedPayload":{"position":{"x":42636.198275976094,"y":28799.555013396635},"targetPosition":{"x":42436.14135357761,"y":29099.04809298164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"3a96fe18-f607-4a4e-9921-31b507475885","savedPayload":{"position":{"x":42438.1614413147,"y":29100.30420086799},"targetPosition":{"x":42080.37565816827,"y":29635.83788162232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f6114149-90fb-45a7-a42a-6ab052a9980a","savedPayload":{"position":{"x":43074.64510700322,"y":29088.795420231665},"targetPosition":{"x":42740.63494825482,"y":29589.031414236302},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"315b67c6-84cf-4370-a4fd-a8209c2532c9","savedPayload":{"position":{"x":43165.96984719894,"y":29163.695538605894},"targetPosition":{"x":42740.63494825482,"y":29589.031414236302},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.sessionReferences === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Session Reference.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.sessionReferences)\n \n setup(templateClone, target, exchange, 'Exchange Sessions', 'sessionReferences')\n templateClone.singleMarketData.payload.referenceParent = market\n\n templateClone.templateType = 'Forward Testing Session'\n return templateClone\n }\n}","id":"8635fe81-b9b5-4a95-a6d0-950584c2787c","savedPayload":{"position":{"x":43240.87263989456,"y":29255.01973980808},"targetPosition":{"x":42740.63494825482,"y":29589.031414236302},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"3bb7924d-49b4-4748-83f5-5067dd8f3ef5","savedPayload":{"position":{"x":42740.63494825482,"y":29589.031414236302},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Testing Environment Task Manager","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Paper & Backtesting Sessions","tasks":[{"type":"Task","name":"Paper Trading WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Paper Trading Session","name":"Paper WHB","code":"{\n\"folderName\": \"Paper-WHB\"\n}","parameters":{"type":"Parameters","name":"Parameters","sessionBaseAsset":{"type":"Session Base Asset","name":"","code":"{ \n\"initialBalance\": 0.01,\n\"minimumBalance\": 0.005\n}","id":"7d2a4605-5be7-4b12-9761-0087ce656080","savedPayload":{"position":{"x":43314.347355679805,"y":27886.180495266573},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"sessionQuotedAsset":{"type":"Quoted Asset","name":"","code":"{\n\"initialBalance\": 0\n}","id":"46c16e25-d21d-4ca8-a528-ae7bf85e9d3f","savedPayload":{"position":{"x":43270.11105487198,"y":27610.06167977048},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"finalDatetime\": \"2020-12-31\"\n}","id":"e645ba22-2ba3-4c87-9d62-748477e055dc","savedPayload":{"position":{"x":43458.610110666305,"y":27403.132156057905},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-min\"\n}","id":"9b0e2fa1-fb9e-4854-9657-7157843a0a56","savedPayload":{"position":{"x":43737.762993615695,"y":27421.44478260165},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"072fc7e2-ca96-4c0a-963c-d8697f26a12c","savedPayload":{"position":{"x":43897.65037204082,"y":27651.219207475497},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"New Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"28139c87-d8fb-49e6-92ce-f826f0ef32d3","savedPayload":{"position":{"x":43817.740726622455,"y":27919.157103391895},"targetPosition":{"x":43579.28928007096,"y":27702.1138119903},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"0e80ef27-72f6-42e9-98bd-2266ad8d0902","savedPayload":{"position":{"x":43579.28928007096,"y":27702.1138119903},"targetPosition":{"x":43558.15104818431,"y":28023.96596801331},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"43096f38-2d07-4536-a8de-c1d6dc52e5af","savedPayload":{"position":{"x":43558.15104818431,"y":28023.96596801331},"targetPosition":{"x":43537.2392391117,"y":28345.180697797186},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"h"},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","id":"c169326d-c842-452b-a9b7-e8658e31dcd4","savedPayload":{"position":{"x":43826.45361245783,"y":28203.054822784812},"targetPosition":{"x":43537.2392391117,"y":28345.180697797186},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"67daf900-abb9-4d94-b86b-85ec5820c311","savedPayload":{"position":{"x":43537.2392391117,"y":28345.180697797186},"targetPosition":{"x":43358.19739378843,"y":28612.949684445317},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"e1d9b766-0b00-478d-a179-b4d030811a17","savedPayload":{"position":{"x":43358.19739378843,"y":28612.949684445317},"targetPosition":{"x":43148.07377003864,"y":28927.454634128168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"10914881-2af7-4326-9a48-eb50a5b83b21","savedPayload":{"position":{"x":43148.247239305325,"y":28927.248338917063},"targetPosition":{"x":42790.1695410171,"y":29463.444388464814},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Backtesting WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Backtesting Session","name":"Back WHB","code":"{\n\"folderName\": \"Back-WHB\"\n}","parameters":{"type":"Parameters","name":"Parameters","sessionBaseAsset":{"type":"Session Base Asset","name":"","code":"{\n\"initialBalance\": 1,\n\"minimumBalance\": 0.5\n}","id":"58289171-cb5f-482c-b5f6-445eb6dd33bf","savedPayload":{"position":{"x":44036.02698339722,"y":28363.366059104726},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"sessionQuotedAsset":{"type":"Session Quoted Asset","name":"","code":"{}","id":"59fd49b3-e491-4029-9dae-12cf0a0e48e5","savedPayload":{"position":{"x":44135.88796016322,"y":28102.124337260877},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"initialDatetime\": \"2019-01-01\",\n\"finalDatetime\": \"2020-12-31\"\n}","id":"7bd10192-7a52-4995-98aa-e16129827ca8","savedPayload":{"position":{"x":44402.36557610739,"y":28017.15274644697},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-hs\"\n}","id":"59764b94-aa9f-462a-be17-5b110e18609a","savedPayload":{"position":{"x":44635.11601984425,"y":28172.691104702066},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"f72c1a53-9e2e-49d5-a07d-90d7ab53c6cf","savedPayload":{"position":{"x":44658.68970970164,"y":28451.428632796116},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"3f249546-8932-42c0-a779-b4142cb03d17","savedPayload":{"position":{"x":44455.41819917701,"y":28643.69425984277},"targetPosition":{"x":44357.202378679365,"y":28336.579675132554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2c70a07e-67d1-4157-8524-448842327782","savedPayload":{"position":{"x":44357.202378679365,"y":28336.579675132554},"targetPosition":{"x":44178.11719301108,"y":28604.57168539026},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"58ad6731-33c6-4a8f-8bfd-fbd40d7aa8d0","savedPayload":{"position":{"x":44178.11719301108,"y":28604.57168539026},"targetPosition":{"x":43999.15712266918,"y":28872.29629503109},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","id":"3a49b8dd-94b8-4b8f-9c0d-a140ba4526a8","savedPayload":{"position":{"x":44320.91170982664,"y":28893.613213044515},"targetPosition":{"x":43999.15712266918,"y":28872.29629503109},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"649950c4-79f9-4d35-ba72-a96d67fb9278","savedPayload":{"position":{"x":43999.15712266918,"y":28872.29629503109},"targetPosition":{"x":43710.28876756263,"y":29014.513498728833},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"ef610792-0e38-40b6-8d28-41b6b8d76cb7","savedPayload":{"position":{"x":43710.28876756263,"y":29014.513498728833},"targetPosition":{"x":43370.93380625043,"y":29181.70054572794},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"13ceeef0-8a9f-4da9-a4ce-43bc32323c47","savedPayload":{"position":{"x":43368.53019880899,"y":29178.408421637265},"targetPosition":{"x":42790.1695410171,"y":29463.444388464814},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}}],"id":"e4b004e5-d078-435b-896e-bcbec6684a4c","savedPayload":{"position":{"x":42623.940516318515,"y":29608.370805879204},"targetPosition":{"x":42301.51227538709,"y":29890.981539114837},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"d1632ba7-4823-4174-9caf-8979360c7399","savedPayload":{"position":{"x":43383.9329297536,"y":29441.46216514682},"targetPosition":{"x":42931.807057773796,"y":29838.171591076367},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"cce4bcd5-fa0e-4dda-82a7-28cbf76432a4","savedPayload":{"position":{"x":43452.759505915485,"y":29537.44718063196},"targetPosition":{"x":42931.807057773796,"y":29838.171591076367},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Testing Environment","name":"New Testing Environment","id":"2ae685a1-25c6-4bd5-b559-d83e373e8d45"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Testing Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Testing Environment.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n \n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n if (process.session !== undefined) {\n if (process.session.parameters !== undefined) {\n if (process.session.parameters.sessionBaseAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.sessionBaseAsset, superAction.payload.parentNode.baseAsset)\n }\n if (process.session.parameters.sessionQuotedAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.sessionQuotedAsset, superAction.payload.parentNode.quotedAsset)\n }\n\n function findExchangeAccountAsset(taskAsset, marketAsset) {\n for (let k = 0; k < superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts.length; k++) {\n let userAccount = superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[k]\n let found = false\n if (userAccount.userAssets !== undefined) {\n for (let m = 0; m < userAccount.userAssets.assets.length; m++) {\n let asset = userAccount.userAssets.assets[m]\n if (asset.payload.referenceParent !== undefined) {\n if (asset.payload.referenceParent.id === marketAsset.payload.referenceParent.id) {\n taskAsset.payload.referenceParent = asset\n found = true\n break\n }\n }\n }\n }\n if (found === true) { break }\n }\n }\n }\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"49b39b49-1d4c-405c-a9f8-955e0ecc9a71","savedPayload":{"position":{"x":43501.47288880676,"y":29645.046577239078},"targetPosition":{"x":42931.807057773796,"y":29838.171591076367},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"414cdf9f-dc5b-4c23-ac5c-0e7a098b2143","savedPayload":{"position":{"x":42931.807057773796,"y":29838.171591076367},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Production Environment Task Manager","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Fwd & Live Sessions","tasks":[{"type":"Task","name":"Fwd Testing WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Forward Testing Session","name":"Fwd WHB","code":"{\n\"folderName\": \"Fwd-WHB\",\n\"balancePercentage\": 10\n}","parameters":{"type":"Parameters","name":"Parameters","sessionBaseAsset":{"type":"Session Base Asset","name":"","code":"{ \n\"initialBalance\": 0.01,\n\"minimumBalance\": 0.005\n}","id":"19a13131-9554-43ef-860b-d994b9804a0e","savedPayload":{"position":{"x":43949.02209402912,"y":28514.744495850893},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"sessionQuotedAsset":{"type":"Session Quoted Asset","name":"","code":"{\n\"initialBalance\": 0\n}","id":"00ee3b70-7e94-4b71-aa64-05f6a699b080","savedPayload":{"position":{"x":43977.81112139866,"y":28236.59236923798},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"finalDatetime\": \"2020-12-31\"\n}","id":"8d94c05a-b82d-4fd9-8552-dcf19fb01ba1","savedPayload":{"position":{"x":44213.44333414086,"y":28085.505562610808},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-min\"\n}","id":"e6e657e8-1049-40e7-aeec-b57429e34a59","savedPayload":{"position":{"x":44478.339217712455,"y":28175.492208004023},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"57de0f1c-8cef-462a-bb88-764cfec67516","savedPayload":{"position":{"x":44573.309327218114,"y":28438.61215850976},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"New Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"70c32855-5085-4e45-be3c-cfdb212ef266","savedPayload":{"position":{"x":44426.715336862566,"y":28676.91519870268},"targetPosition":{"x":44252.09762841825,"y":28406.000583471792},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c5989e16-cde7-414d-9296-e28d805e216a","savedPayload":{"position":{"x":44252.09762841825,"y":28406.000583471792},"targetPosition":{"x":44148.7422061206,"y":28711.062917859083},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"47651cad-aee5-405e-9d40-b4787c49d09e","savedPayload":{"position":{"x":44148.7422061206,"y":28711.062917859083},"targetPosition":{"x":44045.11308194365,"y":29016.158202849896},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","keyInstance":{"type":"Key Instance","name":"","id":"5677d06d-c674-476d-a9a9-fc231616f478","savedPayload":{"position":{"x":44677.4538514425,"y":28891.11030273736},"targetPosition":{"x":44361.54848844125,"y":28953.513309627917},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ac49275e-4fe1-480f-be02-37665547a777","savedPayload":{"position":{"x":44361.54848844125,"y":28953.513309627917},"targetPosition":{"x":44045.11308194365,"y":29016.158202849896},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"57deee32-22b7-4eec-8c9a-9b971d1aeb1a","savedPayload":{"position":{"x":44045.11308194365,"y":29016.158202849896},"targetPosition":{"x":43803.03273271357,"y":29228.27863694877},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"8910dee0-b699-4a35-85ee-a2b3bbdac18e","savedPayload":{"position":{"x":43803.03273271357,"y":29228.27863694877},"targetPosition":{"x":43518.575577642056,"y":29477.69782587427},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"70fd8717-56d5-4ad8-b6ed-3050decedf19","savedPayload":{"position":{"x":43511.58765394764,"y":29481.161545691997},"targetPosition":{"x":43028.08397698985,"y":29905.30941876938},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Live Trading WHB","bot":{"type":"Trading Bot Instance","code":" {}","processes":[{"type":"Trading Process Instance","name":"Multi-Period","code":" {\n \"process\": \"Multi-Period\"\n }","session":{"type":"Live Trading Session","name":"Live Trading WHB","code":"{\n\"folderName\": \"Live-WHB\"\n}","parameters":{"type":"Parameters","name":"Parameters","sessionBaseAsset":{"type":"Session Base Asset","name":"","code":"{ \n\"initialBalance\": 0.01,\n\"minimumBalance\": 0.005\n}","id":"1c0d2a17-cfd5-4eb4-8322-388ad7472fea","savedPayload":{"position":{"x":44511.69115776187,"y":29163.28766291385},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"sessionQuotedAsset":{"type":"Session Quoted Asset","name":"","code":"{\n\"initialBalance\": 0\n}","id":"43d84134-6e65-4ba9-93fb-0d8cb705e90a","savedPayload":{"position":{"x":44675.41944631209,"y":28937.14672767146},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeRange":{"type":"Time Range","name":"New Time Range","code":"{\n\"finalDatetime\": \"2020-12-31\"\n}","id":"16b7de55-dd83-4d7b-a1f8-45f28200a6f6","savedPayload":{"position":{"x":44954.1838102591,"y":28924.144732198314},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrame":{"type":"Time Frame","name":"New Time Frame","code":"{\n\"value\": \"01-min\"\n}","id":"8c2d281c-7cd7-4f3e-8411-777eaec18ed5","savedPayload":{"position":{"x":45138.344553177376,"y":29134.142576562248},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"slippage":{"type":"Slippage","name":"New Slippage","code":"{\n\"positionRate\": 0.1,\n\"stopLoss\": 0.1,\n\"takeProfit\": 0.1\n}","id":"dc21103a-37a9-4c67-a456-a3d35c14e9ad","savedPayload":{"position":{"x":45089.04722701449,"y":29408.829825358807},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"feeStructure":{"type":"Fee Structure","name":"New Fee Structure","code":"{\n\"maker\": 0.1,\n\"taker\": 0.1\n}","id":"c0a305e0-3a8b-428e-b005-117bd35270dc","savedPayload":{"position":{"x":44843.50938494748,"y":29541.60389632369},"targetPosition":{"x":44827.797086714076,"y":29220.397958400925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"325c8283-9e06-4711-9791-49994e1eb24f","savedPayload":{"position":{"x":44827.797086714076,"y":29220.397958400925},"targetPosition":{"x":44586.3641707821,"y":29432.324069027018},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"e70a2d0f-329a-410e-b3ed-fbf1968cac32","savedPayload":{"position":{"x":44586.3641707821,"y":29432.324069027018},"targetPosition":{"x":44344.60660428931,"y":29644.1656163963},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","id":"a239a374-497a-43f9-8382-5277410b1c2d"}}},"marketReference":{"type":"Market Reference","name":"","keyInstance":{"type":"Key Instance","name":"","id":"3fa5a1c3-ec84-4335-b318-7ed4c6e412a9","savedPayload":{"position":{"x":44953.33353749047,"y":29851.30281583057},"targetPosition":{"x":44649.290571908976,"y":29747.76088866544},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Exchange Account Key","name":"Key 1","id":"d2893836-c3d5-449f-a132-971542ef0c0d"}}},"id":"a6c24af9-77fd-4179-bb83-22928acce259","savedPayload":{"position":{"x":44649.290571908976,"y":29747.76088866544},"targetPosition":{"x":44344.60660428931,"y":29644.1656163963},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"6053e090-2c8e-4c1b-8eb4-eb3032f5cb9d","savedPayload":{"position":{"x":44344.60660428931,"y":29644.1656163963},"targetPosition":{"x":44029.65068525114,"y":29706.589257726668},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period","id":"6f95cc67-bad0-48dc-bb82-0075c43cb202"}}}],"id":"67b86977-56e8-4cb9-aa4f-0ae1b8d6d9f3","savedPayload":{"position":{"x":44029.65068525114,"y":29706.589257726668},"targetPosition":{"x":43658.77696384793,"y":29780.18960006669},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"8b8401d2-fbd3-4514-9a24-6236941f8572","savedPayload":{"position":{"x":43658.94710506227,"y":29780.009588673463},"targetPosition":{"x":43028.08397698985,"y":29905.30941876938},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"122a1f16-06c2-4e63-9c9b-0b1b6532ef6e","savedPayload":{"position":{"x":42836.228026148114,"y":30000.0708266346},"targetPosition":{"x":42451.60692532848,"y":30189.593514413704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"l"}}},"id":"7154ffab-e458-47a9-98ab-716d7db899c2","savedPayload":{"position":{"x":43591.39888865664,"y":29862.167594940413},"targetPosition":{"x":43051.97981248752,"y":30128.30519050224},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"efbaa91d-f097-4fda-b373-8b9394dae215","savedPayload":{"position":{"x":43633.03656791882,"y":29972.696256647498},"targetPosition":{"x":43051.97981248752,"y":30128.30519050224},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Production Environment","name":"New Production Environment","id":"32656457-f603-416a-b805-546642094dc4"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Production Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Production Environment.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n\n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n if (process.marketReference.keyInstance !== undefined) {\n process.marketReference.keyInstance.payload.referenceParent = key\n }\n if (process.session !== undefined) {\n if (process.session.parameters !== undefined) {\n if (process.session.parameters.sessionBaseAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.sessionBaseAsset, superAction.payload.parentNode.baseAsset)\n }\n if (process.session.parameters.sessionQuotedAsset !== undefined) {\n findExchangeAccountAsset(process.session.parameters.sessionQuotedAsset, superAction.payload.parentNode.quotedAsset)\n }\n\n function findExchangeAccountAsset(taskAsset, marketAsset) {\n for (let k = 0; k < superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts.length; k++) {\n let userAccount = superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[k]\n let found = false\n if (userAccount.userAssets !== undefined) {\n for (let m = 0; m < userAccount.userAssets.assets.length; m++) {\n let asset = userAccount.userAssets.assets[m]\n if (asset.payload.referenceParent !== undefined) {\n if (asset.payload.referenceParent.id === marketAsset.payload.referenceParent.id) {\n taskAsset.payload.referenceParent = asset\n found = true\n break\n }\n }\n }\n }\n if (found === true) { break }\n }\n }\n }\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"7504b088-dc0b-4a67-b673-150524eb3430","savedPayload":{"position":{"x":43652.240225669295,"y":30089.23818002004},"targetPosition":{"x":43051.97981248752,"y":30128.30519050224},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"aaef47a3-030f-4bba-a297-72973f84a6a7","savedPayload":{"position":{"x":43051.97981248752,"y":30128.30519050224},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Masters Data Mining Tasks","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Masters","tasks":[{"type":"Task","name":"OHKCVs","bot":{"type":"Sensor Bot Instance","name":"Exchange Raw Data","code":" {\n \"repo\": \"Exchange-Raw-Data-Sensor-Bot\",\n \"startDate\": \"2015-01-01\"\n }","processes":[{"type":"Sensor Process Instance","name":"Exchange Raw Data Historic-OHLCVs","marketReference":{"type":"Market Reference","name":"","id":"fa969ad9-f967-4c42-9a7b-e3ca5c53a2a4","savedPayload":{"position":{"x":44563.752766473845,"y":28726.51204419318},"targetPosition":{"x":44505.18351145028,"y":29022.185768977342},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"7321618d-f188-4909-ae51-bff16e83e85c","savedPayload":{"position":{"x":44505.18351145028,"y":29022.185768977342},"targetPosition":{"x":44446.73000839822,"y":29317.919871736813},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Historic-OHLCVs","id":"5eb11d6a-022e-4f60-97c4-d6cf90b249dc"}}}],"id":"66324e06-a787-4389-ba0a-f6158224532e","savedPayload":{"position":{"x":44446.73000839822,"y":29317.919871736813},"targetPosition":{"x":44388.44322497823,"y":29613.907569482504},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"02b2b5ef-8fbb-4234-847b-9e9381e39e40","savedPayload":{"position":{"x":44388.44322497823,"y":29613.907569482504},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Candles & Volumes","bot":{"type":"Indicator Bot Instance","name":"Candles Volumes","code":" {\n \"repo\": \"Candles-Volumes-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"Candles Volumes Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"65a2cdb9-af06-458b-ae81-52a922d32874","savedPayload":{"position":{"x":44967.25960209239,"y":28881.73820503715},"targetPosition":{"x":44870.441375474875,"y":29167.311094532386},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"fe3e9758-12d8-46da-a7e9-968a340f0ae5","savedPayload":{"position":{"x":44870.441375474875,"y":29167.311094532386},"targetPosition":{"x":44773.69751260277,"y":29452.87114374833},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"9f683143-a33f-42c1-9fa8-c601f01ee4e1"}}},{"type":"Indicator Process Instance","name":"Candles Volumes Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"5ba98b0e-e71d-4c91-a3b2-ca98f7bf3e1b","savedPayload":{"position":{"x":45453.09059939077,"y":28856.68518068597},"targetPosition":{"x":45113.42999916721,"y":29154.70174934311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"e3fe5518-65d4-4413-9d1f-624025766f97","savedPayload":{"position":{"x":45113.42999916721,"y":29154.70174934311},"targetPosition":{"x":44773.69751260277,"y":29452.87114374833},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"980ceb1d-7be6-454f-90d9-01ba1fd4a65d"}}}],"id":"80415df6-6aca-42d5-b91d-6d5d6326f72f","savedPayload":{"position":{"x":44773.69751260277,"y":29452.87114374833},"targetPosition":{"x":44606.29852269913,"y":29703.881918996256},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"a2a8253d-ea54-431a-a623-627a9d80c96d","savedPayload":{"position":{"x":44606.29852269913,"y":29703.881918996256},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Candles Channels","bot":{"type":"Indicator Bot Instance","name":"Candles Channels","code":"{}","processes":[{"type":"Indicator Process Instance","name":"Candle Channels Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Binance BTC/USDT","id":"cb0330ef-a176-4e88-b09e-17c5c0ed8893","savedPayload":{"position":{"x":45421.3393568106,"y":29249.255430321416},"targetPosition":{"x":45222.451918474486,"y":29475.929696456333},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"5489751f-5ce4-40d0-9854-5bcfdc810269"}}},"id":"606c5e74-aa46-47d9-82c9-e1ff4a627ebe","savedPayload":{"position":{"x":45222.451918474486,"y":29475.929696456333},"targetPosition":{"x":45023.650296285596,"y":29702.579362584125},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"2ab8c00c-8e15-4bdc-87fa-291c8d4edadb"}}},{"type":"Indicator Process Instance","name":"Candle Channels Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Binance BTC/USDT","id":"de1108b1-a773-4d54-b32b-b2931011f115","savedPayload":{"position":{"x":45879.664842377,"y":29411.96516858365},"targetPosition":{"x":45451.73624737675,"y":29557.237066035806},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"5489751f-5ce4-40d0-9854-5bcfdc810269"}}},"id":"6c0075a2-e741-45b7-8b6c-66d2faf95ad8","savedPayload":{"position":{"x":45451.73624737675,"y":29557.237066035806},"targetPosition":{"x":45023.650296285596,"y":29702.579362584125},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"0de6dd1f-5bca-4f66-a5db-cd068dd4190f"}}}],"id":"d29785c9-1789-45f5-88a1-17fa13a383bb","savedPayload":{"position":{"x":45023.650296285596,"y":29702.579362584125},"targetPosition":{"x":44772.77564042518,"y":29870.23031124894},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"34ef19e0-5e75-4108-86e5-276f213deea5","savedPayload":{"position":{"x":44772.77564042518,"y":29870.23031124894},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},{"type":"Task","name":"Bollinger Bands & Percentage Bandwidth","bot":{"type":"Indicator Bot Instance","name":"Bollinger Bands","code":" {\n \"repo\": \"Bollinger-Bands-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"Bollinger Bands Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"936109e1-9d5f-4738-9eee-26e98c6675a9","savedPayload":{"position":{"x":45699.89387085827,"y":29762.487102244177},"targetPosition":{"x":45429.335053302355,"y":29895.69561846699},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"69c2cfe4-3a79-45d0-8742-ff3f368431f6","savedPayload":{"position":{"x":45429.335053302355,"y":29895.69561846699},"targetPosition":{"x":45158.858744477104,"y":30028.883442023704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"e598ee86-1e18-4aa1-95c2-64aaf66776ca"}}},{"type":"Indicator Process Instance","name":"Bollinger Bands Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"75d8a16d-5ff3-4771-b32a-bf5ea071e7fc","savedPayload":{"position":{"x":46061.00454182307,"y":30087.712394580292},"targetPosition":{"x":45610.02425587317,"y":30058.313770154724},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"c437054a-3447-486b-ab42-b267b800f96a","savedPayload":{"position":{"x":45610.02425587317,"y":30058.313770154724},"targetPosition":{"x":45158.858744477104,"y":30028.883442023704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"941a0543-0cc0-4a5d-a782-728d2835c100"}}}],"id":"9604d5b6-af02-4ac3-91aa-fa82dcd316bf","savedPayload":{"position":{"x":45158.858744477104,"y":30028.883442023704},"targetPosition":{"x":44862.83944658449,"y":30087.58329134732},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"ed9d924d-7a8b-46da-a4e9-eb121329fe49","savedPayload":{"position":{"x":44862.83944658449,"y":30087.58329134732},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Bollinger Channels & Sub-Channels","bot":{"type":"Indicator Bot Instance","name":"Bollinger Channels","code":" {\n \"repo\": \"Bollinger-Channels-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"Bollinger Channels Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"6e4bd29a-ffe6-4bbd-a114-62a0efe82a12","savedPayload":{"position":{"x":45760.6488820697,"y":30342.828489267125},"targetPosition":{"x":45459.70100509999,"y":30362.234023108005},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"b3c5ca8e-cc9d-4662-96cb-82d7c5801f67","savedPayload":{"position":{"x":45459.70100509999,"y":30362.234023108005},"targetPosition":{"x":45158.82769546795,"y":30381.603232805704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"28be432d-80ca-46b3-859a-55541e24b2b4"}}},{"type":"Indicator Process Instance","name":"Bollinger Channels Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"d6995a0d-dffd-4213-b159-1919fa35f1b8","savedPayload":{"position":{"x":45969.52778588621,"y":30781.448959775782},"targetPosition":{"x":45564.269191446634,"y":30581.63665626878},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"d640b042-3b50-4f02-961a-9f4e9aab1015","savedPayload":{"position":{"x":45564.269191446634,"y":30581.63665626878},"targetPosition":{"x":45158.82769546795,"y":30381.603232805704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"bc15b49d-4547-4bac-a28b-193eb0158fe3"}}}],"id":"88c5bffa-e658-4a99-89b3-b3b03d40d2e6","savedPayload":{"position":{"x":45158.82769546795,"y":30381.603232805704},"targetPosition":{"x":44862.82546003235,"y":30322.600699455408},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"10972cbd-be85-4344-bf34-8aab4dc3a23b","savedPayload":{"position":{"x":44862.82546003235,"y":30322.600699455408},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},{"type":"Task","name":"Webhooks","bot":{"type":"Sensor Bot Instance","name":"Webhooks","code":" {\n \"repo\": \"Webhooks-Sensor-Bot\"\n }","processes":[{"type":"Sensor Process Instance","name":"Webhooks Check-Webhook","marketReference":{"type":"Market Reference","name":"Binance BTC/USDT","id":"2ef284e2-813b-422b-91b1-fd9f4a3289dc","savedPayload":{"position":{"x":41234.40772073858,"y":30225.096264588887},"targetPosition":{"x":40812.801769173064,"y":30219.066258020608},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"5489751f-5ce4-40d0-9854-5bcfdc810269"}}},"id":"4b5cf18d-cd45-4a96-9117-8d5d7781b0ab","savedPayload":{"position":{"x":45274.248143438956,"y":30875.483495476296},"targetPosition":{"x":45023.527910627956,"y":30707.83217071608},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Check-Webhook","id":"69f37820-ab16-4e19-bfd8-386dd6e249a3"}}}],"id":"79548414-ae13-4e0f-99b6-fff96171bbaf","savedPayload":{"position":{"x":45023.527910627956,"y":30707.83217071608},"targetPosition":{"x":44772.69288254182,"y":30539.965434132333},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"5aa3a8bc-9fda-4e11-a56d-5655b148bfa7","savedPayload":{"position":{"x":44772.69288254182,"y":30539.965434132333},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"Resistances & Supports","bot":{"type":"Indicator Bot Instance","name":"Resistances & Supports","code":" {\n \"repo\": \"\"\n }","processes":[{"type":"Indicator Process Instance","name":"Resistances and Supports Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"","id":"e28673ef-1555-4468-83f6-9011026ef257","savedPayload":{"position":{"x":45452.66805723561,"y":31553.156804953058},"targetPosition":{"x":45113.06001290915,"y":31255.234887033326},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"1d60f7c3-f431-43f0-aaf3-6b25c092a7d3","savedPayload":{"position":{"x":45113.06001290915,"y":31255.234887033326},"targetPosition":{"x":44773.34200526984,"y":30957.178797188724},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"5d9b2729-43fb-4e7e-aa4f-b8477c424e51"}}},{"type":"Indicator Process Instance","name":"Resistances and Supports Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"","id":"5c8850db-00c6-4607-b153-469534df6f68","savedPayload":{"position":{"x":44966.73206008909,"y":31528.118995498546},"targetPosition":{"x":44870.00048688255,"y":31242.66032093637},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f38aa087-739d-4c9c-ac81-c5eaf4750f6e","savedPayload":{"position":{"x":44870.00048688255,"y":31242.66032093637},"targetPosition":{"x":44773.34200526984,"y":30957.178797188724},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"be19d91b-9337-4f84-96b3-f4e54ea12df5"}}}],"id":"12391346-c72a-44d7-8574-17fd5ed22657","savedPayload":{"position":{"x":44773.34200526984,"y":30957.178797188724},"targetPosition":{"x":44606.10518045995,"y":30706.220760937824},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"39e297c4-fc60-4df1-a76d-c59f45b6a090","savedPayload":{"position":{"x":44606.10518045995,"y":30706.220760937824},"targetPosition":{"x":44272.00779942372,"y":30204.946262238485},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"8e0023df-5beb-4187-a53b-908c363861f9","savedPayload":{"position":{"x":44273.186396693905,"y":30204.885859141745},"targetPosition":{"x":43682.65778612944,"y":30322.206497874296},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"j"}}},"id":"930ad389-2e02-405d-a608-628df967e702","savedPayload":{"position":{"x":43682.89779132531,"y":30322.114446203646},"targetPosition":{"x":43092.9570586135,"y":30439.530985154168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"216101a4-697c-41c6-989c-a5f8f181c624","savedPayload":{"position":{"x":43694.50778488079,"y":30439.5301984526},"targetPosition":{"x":43092.9570586135,"y":30439.530985154168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Mining","name":"New Data Mining","id":"7449a608-32ca-4285-94d1-c96791e2817f"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Data Mining') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n \n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"c1cbecc9-8340-4435-a0ab-140334ba104d","savedPayload":{"position":{"x":43682.8911800849,"y":30556.999036454978},"targetPosition":{"x":43092.9570586135,"y":30439.530985154168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"ddd80520-a052-464b-a7b8-23b72387086f","savedPayload":{"position":{"x":43092.9570586135,"y":30439.530985154168},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Sparta Data Mining Tasks","templateStructure":{"type":"Template Structure","name":"New Template Structure","taskManagers":{"type":"Task Manager","name":"Sparta","tasks":[{"type":"Task","name":"SMA","bot":{"type":"Indicator Bot Instance","name":"SMA","code":" {\n\"repo\": \"SMA-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"SMA Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"592304dd-b412-4f42-9f24-1c2b6305538a","savedPayload":{"position":{"x":44071.61797125626,"y":29269.179122287296},"targetPosition":{"x":44017.22604407985,"y":29586.166432086655},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"4829d508-b32b-402c-8d27-4493549196b5","savedPayload":{"position":{"x":44017.22604407985,"y":29586.166432086655},"targetPosition":{"x":43962.84190684965,"y":29903.09502706654},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"456269f6-9849-4d5f-9c8c-7cd7d1eef297"}}},{"type":"Indicator Process Instance","name":"SMA Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"4de8af37-754b-4178-9960-49f8dd9fea12","savedPayload":{"position":{"x":45168.8488344655,"y":29458.147255078686},"targetPosition":{"x":44565.86589926591,"y":29680.585441818144},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"5f34959e-a63c-4298-8323-a16f75e23431","savedPayload":{"position":{"x":44565.86589926591,"y":29680.585441818144},"targetPosition":{"x":43962.84190684965,"y":29903.09502706654},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"333ae5d7-7ec1-4f08-b206-91e3b4f2c99f"}}}],"id":"6db29c31-152b-472a-852e-0e4a36bec524","savedPayload":{"position":{"x":43962.84190684965,"y":29903.09502706654},"targetPosition":{"x":43551.90647643782,"y":30397.266512500442},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"20c34f88-6b58-474b-b69d-02712765c0db","savedPayload":{"position":{"x":43551.90647643782,"y":30397.266512500442},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"EMA","bot":{"type":"Indicator Bot Instance","name":"EMA","code":" {\n\"repo\": \"EMA-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"EMA Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"64c7ca91-01ce-4dab-8b29-7cfa8e1f19a4","savedPayload":{"position":{"x":44848.06740265459,"y":30126.954299288456},"targetPosition":{"x":44617.64305938516,"y":30351.280902615003},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"31761ef4-c98b-4657-a37f-652eff5340ea","savedPayload":{"position":{"x":44617.64305938516,"y":30351.280902615003},"targetPosition":{"x":44387.27143213535,"y":30575.564915785017},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"278c34a7-199d-400e-8a06-087149e3d391"}}},{"type":"Indicator Process Instance","name":"EMA Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"7610e87b-a8c3-475c-a44f-665e26614ad8","savedPayload":{"position":{"x":45624.60027321795,"y":30924.208529781736},"targetPosition":{"x":45005.977709353945,"y":30749.876937252106},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"c4d61b48-9c23-47f1-937b-1729767a26f6","savedPayload":{"position":{"x":45005.977709353945,"y":30749.876937252106},"targetPosition":{"x":44387.27143213535,"y":30575.564915785017},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"943ea24f-ee24-4eae-9b4b-e8a73122b1c2"}}}],"id":"34656b7b-826e-4ebd-943a-407d8c91ea24","savedPayload":{"position":{"x":44387.27143213535,"y":30575.564915785017},"targetPosition":{"x":43764.266809147426,"y":30733.667438768847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"28be18c7-c387-435e-ae7d-dfeca201bce5","savedPayload":{"position":{"x":43764.266809147426,"y":30733.667438768847},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"MACD","bot":{"type":"Indicator Bot Instance","name":"MACD","code":" {\n\"repo\": \"MACD-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"MACD Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"4f7ed8bc-c0b7-4930-80c3-67c7d5378066","savedPayload":{"position":{"x":44971.81020849867,"y":31276.620146946167},"targetPosition":{"x":44653.49004123155,"y":31322.55467880252},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"9cc4175f-28ad-4307-a8d6-0df303a02be1","savedPayload":{"position":{"x":44653.49004123155,"y":31322.55467880252},"targetPosition":{"x":44335.23861007668,"y":31368.479242651025},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"44019efe-950a-4eec-a546-cd2ee26818b9"}}},{"type":"Indicator Process Instance","name":"MACD Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"3c7ff3c1-be17-4c7d-ba0a-1cc66a41970b","savedPayload":{"position":{"x":45131.12724051729,"y":32377.93040356555},"targetPosition":{"x":44733.22183307022,"y":31873.22574960204},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"a585a9e1-e18d-40b6-b364-75f01e840a4b","savedPayload":{"position":{"x":44733.22183307022,"y":31873.22574960204},"targetPosition":{"x":44335.23861007668,"y":31368.479242651025},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"8e5db945-8507-4489-9288-21cfa1e9301c"}}}],"id":"525c3ef8-145e-4919-bf2d-983db99a5cdb","savedPayload":{"position":{"x":44335.23861007668,"y":31368.479242651025},"targetPosition":{"x":43738.24708451567,"y":31130.308446359704},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"b652ba10-fb7a-4a93-bd24-9500bb7d25ec","savedPayload":{"position":{"x":43738.24708451567,"y":31130.308446359704},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Task","name":"RSI","bot":{"type":"Indicator Bot Instance","name":"RSI","code":" {\n\"repo\": \"RSI-Indicator-Bot\"\n }","processes":[{"type":"Indicator Process Instance","name":"RSI Multi-Period-Market","code":" {\n \"process\": \"Multi-Period-Market\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"002570f4-0254-46d4-ba77-c23598fd8e51","savedPayload":{"position":{"x":44395.607564219106,"y":32279.346270225502},"targetPosition":{"x":44111.06304717549,"y":32129.543698932317},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"16a0815f-b793-4ca8-aa09-78f9f447f110","savedPayload":{"position":{"x":44111.06304717549,"y":32129.543698932317},"targetPosition":{"x":43826.594538981204,"y":31979.768305805377},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Market","id":"96f31416-5090-428a-be83-985a0b3ab275"}}},{"type":"Indicator Process Instance","name":"RSI Multi-Period-Daily","code":" {\n \"process\": \"Multi-Period-Daily\"\n }","marketReference":{"type":"Market Reference","name":"Bitfinex BTC/USDT","id":"a1c6a73c-d518-478c-b1bb-cb1b0b4f08db","savedPayload":{"position":{"x":43876.85035087776,"y":33264.29116567567},"targetPosition":{"x":43851.71485258066,"y":32622.075786304355},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"b734f59f-f29c-450b-a6e4-6d2345a1082a"}}},"id":"123f248b-aa95-4d1c-a6c2-9fc70fc2b816","savedPayload":{"position":{"x":43851.71485258066,"y":32622.075786304355},"targetPosition":{"x":43826.594538981204,"y":31979.768305805377},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"referenceParent":{"type":"Process Definition","name":"Multi-Period-Daily","id":"f1177bcd-22dc-425b-8f30-878844aa82b8"}}}],"id":"007d0fe3-9637-4a4f-8078-730af1716e1b","savedPayload":{"position":{"x":43826.594538981204,"y":31979.768305805377},"targetPosition":{"x":43483.775300077,"y":31436.11470464454},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"id":"da1cddfc-5d56-46e7-b14a-0b21127cf6e3","savedPayload":{"position":{"x":43483.775300077,"y":31436.11470464454},"targetPosition":{"x":43140.801983244026,"y":30891.950600638847},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}}],"id":"6907a34a-7247-4717-8be3-154ebc2cacfc","savedPayload":{"position":{"x":42927.695684739876,"y":30878.09191280621},"targetPosition":{"x":42499.798120847416,"y":30850.102649976},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"j"}}},"id":"ac392204-2e14-4744-a636-063c2bf461ae","savedPayload":{"position":{"x":43652.185746548756,"y":30789.964498250916},"targetPosition":{"x":43051.954111996405,"y":30750.842818950958},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"4d93f230-422c-4a10-8e6d-50381aa60aba","savedPayload":{"position":{"x":43632.969464539834,"y":30906.514370388715},"targetPosition":{"x":43051.954111996405,"y":30750.842818950958},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Mining","name":"New Data Mining","id":"7449a608-32ca-4285-94d1-c96791e2817f"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.taskManagers === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Task Manager.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Data Mining') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.taskManagers)\n \n setup(templateClone, target, exchange, 'Exchange Tasks', 'taskManagers')\n for (let i = 0; i < templateClone.tasks.length; i++) {\n let task = templateClone.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n process.marketReference.payload.referenceParent = market\n }\n }\n }\n }\n }\n\n return templateClone\n }\n}","id":"b2fdaf25-bbd2-476a-93ed-faf23b3bfa00","savedPayload":{"position":{"x":43591.30937899207,"y":31017.075000840647},"targetPosition":{"x":43051.954111996405,"y":30750.842818950958},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"83556c43-8483-48e3-be7e-36f9dbb70f08","savedPayload":{"position":{"x":43051.954111996405,"y":30750.842818950958},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Install Time Machine","templateStructure":{"type":"Template Structure","name":"New Template Structure","timeMachines":{"type":"Time Machine","name":"My Time Machine","code":"{}","timeScale":{"type":"Time Scale","name":"New Time Scale","code":"{\n \"fromDate\": \"2018-12-30T23:22:24.048Z\",\n \"toDate\": \"2019-01-10T19:44:56.900Z\",\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"acf304c4-443a-4a57-880e-13d330e754c4","savedPayload":{"position":{"x":44972.262947294315,"y":31118.718470440148},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"offset\": 0,\n \"minValue\": 3540,\n \"maxValue\": 4069.8,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"153feb9a-76e1-4838-ae3e-33cacd7ca646","savedPayload":{"position":{"x":45091.17064358791,"y":31222.70534719221},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{\"value\":\"24-hs\"}","id":"7890a24e-5bf5-450f-bb35-f23b3eb495f6","savedPayload":{"position":{"x":45178.77949805262,"y":31353.610402114664},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"timelineCharts":[{"type":"Timeline Chart","name":"Indicators","layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 2,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"on\",\n \"showPanels\": false\n}","id":"38e64db1-376d-45de-a92c-a6fb25282507","savedPayload":{"position":{"x":45798.08358329995,"y":30783.47112133315},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Candles","id":"884a5d08-b607-4b2b-8e7a-8adeb7e8dd4a"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"9ad6d912-ff9c-45c8-bfce-bcd0d86e3977","savedPayload":{"position":{"x":45898.37673360377,"y":30788.25119234455},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Bollinger Bands","id":"68df01d8-d9a8-400c-aa76-8da44c5dc040"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"6c5e7c7f-a010-4d59-8299-d8d4534aa7f8","savedPayload":{"position":{"x":45996.05938571397,"y":30809.588522972816},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Bollinger Channels Objects","id":"632f0f98-eb2c-4820-9943-662a4903be81"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"41db7192-ec70-4f95-85dd-343bfa05258e","savedPayload":{"position":{"x":46088.671022938994,"y":30846.72421469604},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Bollinger Sub-Channels Objects","id":"88a1ca81-b317-4ab2-b651-c8fa566e8e28"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"13c5c367-3758-4f8f-883c-ba6f1ee3bef2","savedPayload":{"position":{"x":46173.797322491286,"y":30898.58369972401},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Candle-Stairs","id":"5979844b-35e7-4c04-938f-18e6156352a0"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"0fac6cd3-3172-4854-9ce2-f2827023f503","savedPayload":{"position":{"x":46249.17664768098,"y":30963.730987652525},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Popular SMAs","id":"5bce195f-7d08-4281-bb9d-ca1e9417068e"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"88c7874f-9372-4dc5-9bb1-491ca58ceded","savedPayload":{"position":{"x":46312.78393052083,"y":31040.384533238812},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base7 SMAs","id":"b9145946-1899-4fac-b531-47ed600873be"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"6570c568-8d96-427f-9dbe-822d0fe9f900","savedPayload":{"position":{"x":46362.900460538505,"y":31126.454059169817},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base11 SMAs","id":"c9daf265-f452-4372-a8a9-6ed9c0d7a0ed"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"a133263e-3eca-4d4a-b04e-8f40e5ec78ba","savedPayload":{"position":{"x":46398.167326095994,"y":31219.59432824167},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Popular EMAs","id":"fba1f18e-dd5b-48e6-82b1-ff04a624f090"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"32fcaa2a-af1d-4aa6-adf0-36dd24759ca4","savedPayload":{"position":{"x":46417.62588351845,"y":31317.267577006704},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base7 EMAs","id":"64f059bf-66e2-463c-aed1-800b5fa195ab"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"3d5d19b9-481d-4542-b279-06303aa9edef","savedPayload":{"position":{"x":46420.70567572493,"y":31416.809466474464},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Base11 EMAs","id":"5b6e48e5-259f-44f3-b40e-a29982204c95"}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"04f846d4-f203-4eed-807d-214655d4551b","savedPayload":{"position":{"x":46407.31337137963,"y":31515.483822428057},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"10e19911-11b2-4b8d-86fa-b7a5de9f93e4","savedPayload":{"position":{"x":46377.87367543775,"y":31610.607629023725},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"5666253c-62f6-46f2-91fa-c19b44a5932a","savedPayload":{"position":{"x":46333.185880681754,"y":31699.591341637817},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"aae1b1f8-35b5-4c41-b5c9-64d80a97cc38","savedPayload":{"position":{"x":46274.467312818415,"y":31780.011584392756},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"434c3396-d53e-434b-aa8b-c27a170c9ed9","savedPayload":{"position":{"x":46203.31676678368,"y":31849.67669835976},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"9e784e59-be45-4895-9174-75d3dabfd45b","savedPayload":{"position":{"x":46121.67361107911,"y":31906.68977984249},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"3ad3e53b-53d4-4c61-a80d-e3f5a679b2cd","savedPayload":{"position":{"x":46028.33674024209,"y":31950.48529383432},"targetPosition":{"x":45818.52076736195,"y":31385.59859580079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"5bfa0541-b9bb-4dc6-a5a0-5684278c2a16","savedPayload":{"position":{"x":45819.36840869368,"y":31385.419815840156},"targetPosition":{"x":45228.931388073186,"y":31502.74417203152},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":0,"y":80},"width":150,"height":190,"radius":0}}},"id":"63c36d69-c222-4687-a535-c147abcfb9a9","savedPayload":{"position":{"x":45229.38672237636,"y":31502.662848045977},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Volumes","rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"offset\": 0,\n \"minValue\": 3496.3969296811883,\n \"maxValue\": 12400.41819345618,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"6f07d76a-363a-4200-a7f4-ee9daf700744","savedPayload":{"position":{"x":43955.58254498447,"y":31504.999869023693},"targetPosition":{"x":43630.44688258736,"y":31530.628144529433},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"a6350c1f-64b3-4b13-921b-aea1eb09b6c1","savedPayload":{"position":{"x":44162.92610911858,"y":31723.43961416986},"targetPosition":{"x":43951.317213648734,"y":31685.17817589269},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Volumes","id":"4ec71a01-3d83-4e8e-9780-bcb700dcf66a"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"3b5badc4-8160-4c8f-ae19-8db7970e581d","savedPayload":{"position":{"x":44114.39338158986,"y":31825.20197219218},"targetPosition":{"x":43951.317213648734,"y":31685.17817589269},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Volume-Stairs","id":"dd1f89bc-3c2f-4fc4-a5f1-de7a34e0997e"}}}],"id":"929661b5-acce-457c-a8cc-505a64cfacf2","savedPayload":{"position":{"x":43951.317213648734,"y":31685.17817589269},"targetPosition":{"x":43630.44688258736,"y":31530.628144529433},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":150,"y":80},"width":150,"height":40,"radius":0}}},"id":"6f781018-392b-4ba2-b00f-68d4c5498f2b","savedPayload":{"position":{"x":45239.67954677261,"y":31659.506072974258},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Market","name":"BTC/USDT","id":"d4098b73-7e25-434e-8fd9-342393cbb400"}}},{"type":"Timeline Chart","name":"Oscilators","rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"minValue\": -2,\n \"maxValue\": 2,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"5074db98-f742-4771-a68e-a001b9c45182","savedPayload":{"position":{"x":43911.24885028547,"y":31610.221317602256},"targetPosition":{"x":43610.47458913771,"y":31590.810656481015},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"830c5295-25ca-4f08-bc7c-262be9e2fb17","savedPayload":{"position":{"x":44079.22692482198,"y":31836.109592766257},"targetPosition":{"x":43875.02284358463,"y":31767.22077825714},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Percentage Bandwidth","id":"9b5b1b81-c68c-45a5-8123-1a114a24b744"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"8aa6fde7-89cb-43af-bea1-149a0b94c29c","savedPayload":{"position":{"x":44017.101529873595,"y":31929.337889021248},"targetPosition":{"x":43875.02284358463,"y":31767.22077825714},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"RSI14","id":"71351f63-10b4-4f89-ac9f-a8380bb0b75e"}}}],"id":"2f754398-30ac-4afa-8c12-c37013b8a4da","savedPayload":{"position":{"x":43875.02284358463,"y":31767.22077825714},"targetPosition":{"x":43610.47458913771,"y":31590.810656481015},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":300,"y":80},"width":150,"height":40,"radius":0}}},"id":"805c6659-38e2-4959-86d0-cbabc87a8184","savedPayload":{"position":{"x":45208.98090305957,"y":31813.870110660417},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"MACD","rateScale":{"type":"Rate Scale","name":"New Rate Scale","code":"{\n \"minValue\": -2,\n \"maxValue\": 2,\n \"autoMinScale\": true,\n \"autoMaxScale\": true\n}","id":"53e5ea3b-83b8-4460-ac02-c68e2f17c85f","savedPayload":{"position":{"x":43840.620290316496,"y":31789.82237029658},"targetPosition":{"x":43558.71007204624,"y":31694.11687469478},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"left\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"775d50c1-df4b-4d02-9da4-de31dd5fc006","savedPayload":{"position":{"x":43963.892899882354,"y":31948.760513223307},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Percentage Bandwidth","id":"9b5b1b81-c68c-45a5-8123-1a114a24b744"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": false\n}","id":"06f133c7-2bf0-4352-89e7-bd43dc74c3e7","savedPayload":{"position":{"x":43920.721667853606,"y":32029.92468150603},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"RSI14","id":"71351f63-10b4-4f89-ac9f-a8380bb0b75e"}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"ae91005d-2615-4b4e-bcd4-3fb25ef7f510","savedPayload":{"position":{"x":43853.974112885124,"y":32090.426818984488},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Layer","name":"","code":"{ \n\"status\": \"off\"\n}","id":"1939dd65-d12b-436d-8c76-609fc1f776f4","savedPayload":{"position":{"x":43757.20500332351,"y":32127.063274152384},"targetPosition":{"x":43702.617792383055,"y":31853.05810689925},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"68537c49-d84e-4843-9ee2-9aae3f9abef1","savedPayload":{"position":{"x":43767.25622614087,"y":31932.288004103884},"targetPosition":{"x":43558.71007204624,"y":31694.11687469478},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":300,"y":80},"width":150,"height":40,"radius":0}}},"id":"35419f31-46aa-455e-b9bc-1f87a04e7903","savedPayload":{"position":{"x":45139.353748382324,"y":31955.03611259359},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Live Trading","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{ \n\"value\": \"24-hs\"\n}","id":"1ce27f77-c38b-49b1-8ea3-c32687bdb655","savedPayload":{"position":{"x":43783.37261367022,"y":31886.571656235734},"targetPosition":{"x":43521.05702874397,"y":31745.958156152803},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"b672763b-52c9-47c0-8b03-8156a564a6bb","savedPayload":{"position":{"x":43886.41967462734,"y":32119.941637293174},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"247187f3-1aaf-4de1-a7db-c3a550cdbad0"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"e330fc5d-223f-4d13-8251-84be95894b04","savedPayload":{"position":{"x":43842.96035016497,"y":32187.313417315454},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"4deb0411-a8ea-465e-adcd-e0515f291a2d"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"63396270-7226-4fe7-8a77-072a29e73691","savedPayload":{"position":{"x":43780.15086984105,"y":32228.511805578368},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"cec4bf02-ebee-48ad-a0cc-25629e34fd65"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"d08e1626-0cdc-42b6-ab7e-ed35fc9f0f43","savedPayload":{"position":{"x":43677.873644697815,"y":32238.940731261504},"targetPosition":{"x":43690.30875570328,"y":32020.727785295832},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"c9cbb548-60ef-491d-b083-5825573db463"}}}],"id":"c9302ae8-6f99-41c2-86d7-04e6b1cf1817","savedPayload":{"position":{"x":43690.30875570328,"y":32020.727785295832},"targetPosition":{"x":43521.05702874397,"y":31745.958156152803},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1770,"y":80},"width":150,"height":40,"radius":0}}},"id":"02c55b2b-cb65-4131-aa82-de04ce1ca1c6","savedPayload":{"position":{"x":45035.54820924473,"y":32073.374045997527},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Forward Testing","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{ \n\"value\": \"24-hs\"\n}","id":"73a8ce0d-317a-4162-b961-8ff3df64c1c3","savedPayload":{"position":{"x":43684.53879623016,"y":31970.012630752735},"targetPosition":{"x":43425.560345834936,"y":31822.587842697758},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"37d4edc7-51fc-4c9b-93b9-4f0f0e795208","savedPayload":{"position":{"x":43670.419810252366,"y":32282.9624054417},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"456e8c88-ba2b-487d-aa2a-48f6c1144d22"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"0f0b5b97-3962-4eaf-861e-edd447bc4d61","savedPayload":{"position":{"x":43609.82505581555,"y":32335.475436908324},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"6abfb364-1883-488b-ac98-ffa234013e95"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"fcb15fbd-c988-4b80-add2-88c0d20d2f90","savedPayload":{"position":{"x":43537.9321061552,"y":32357.541018716125},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"cdba5840-e1d7-4909-9422-423299403f3c"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"ec034ac9-5e70-44aa-99c7-d0afd8d0f78c","savedPayload":{"position":{"x":43436.996393413065,"y":32338.36265829784},"targetPosition":{"x":43510.26407319966,"y":32132.59925389586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"0dbb4a2c-c32d-4bd1-a410-09ce23613604"}}}],"id":"2bc6080e-b3b8-4240-946b-b1475b407bdc","savedPayload":{"position":{"x":43510.26407319966,"y":32132.59925389586},"targetPosition":{"x":43425.560345834936,"y":31822.587842697758},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1620,"y":80},"width":150,"height":40,"radius":0}}},"id":"de6ca021-6112-41ab-8a4e-5b24682c397e","savedPayload":{"position":{"x":44904.62701626791,"y":32160.8029978036},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Paper Trading","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{ \n\"value\": \"24-hs\"\n}","id":"10022324-b8b0-4d63-ac68-1a52fb1a3bc8","savedPayload":{"position":{"x":43457.36591905919,"y":32128.545321458452},"targetPosition":{"x":43313.35171232311,"y":31869.073818844914},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"89f07041-b4af-47e8-9282-06f889d1873f","savedPayload":{"position":{"x":43418.773208240214,"y":32379.61274434846},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"442bd8c3-1825-4415-84bd-e2f1148b3f92"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"3ca3b1c6-2f56-4a69-a5b1-c6461b339286","savedPayload":{"position":{"x":43345.89520538448,"y":32412.88315685549},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"8ab46f29-e225-42ee-a17e-297b2ebcfdbe"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"8306fbd3-4c33-4796-8a00-259702921276","savedPayload":{"position":{"x":43270.93153284485,"y":32413.314969240215},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"2f5a9ed9-80b6-412f-9d24-47f49653be9b"}}},{"type":"Layer","name":"","code":"{\"status\":\"off\"}","id":"bb634599-045c-4475-a4ed-af6798660d58","savedPayload":{"position":{"x":43179.038096809825,"y":32367.059336454447},"targetPosition":{"x":43307.45180126069,"y":32190.479627192908},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"ddb30433-09f1-44b5-afdc-28a05f218c25"}}}],"id":"0a45ce5c-57f1-442b-b5cf-a86812267f3c","savedPayload":{"position":{"x":43307.45180126069,"y":32190.479627192908},"targetPosition":{"x":43313.35171232311,"y":31869.073818844914},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1470,"y":80},"width":150,"height":40,"radius":0}}},"id":"d5405af5-9184-4017-805d-a7f9f9256524","savedPayload":{"position":{"x":44755.4514495045,"y":32211.35287786325},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Timeline Chart","name":"Backtesting","timeFrameScale":{"type":"Time Frame Scale","name":"New Time Frame Scale","code":"{\"value\":\"01-hs\"}","id":"37fba5b0-3028-4687-bb7b-1162995b2fe2","savedPayload":{"position":{"x":43247.66539437884,"y":32171.10718387298},"targetPosition":{"x":43182.81404257353,"y":31880.527527777554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"layersManager":{"type":"Layers Manager","name":"New Layers Manager","code":"{\n \"visibleLayers\": 0,\n \"panelLocation\": {\n \"upOrDown\": \"up\",\n \"leftOrRight\": \"right\"\n }\n}","layers":[{"type":"Layer","name":"","code":"{\n \"status\": \"off\",\n \"showPanels\": true\n}","id":"068284a9-fd5b-4b68-9103-836637fdcb0b","savedPayload":{"position":{"x":43141.24685193997,"y":32394.974326897627},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Simulation","id":"e7f2581f-1dfd-44eb-82d9-a9be799307e6"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"4cfdb7dd-af3d-444d-abb8-f04d77e73889","savedPayload":{"position":{"x":43061.65443685055,"y":32406.489847624485},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Conditions","id":"24dfc84b-74ec-4d79-9e97-4eb17dd2ac1d"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"1ea11e8c-049c-4ff1-ae9a-0f27d4165127","savedPayload":{"position":{"x":42989.62406345691,"y":32385.45184645436},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Trades","id":"ce87f885-a79b-4675-9cfd-337daed079e5"}}},{"type":"Layer","name":"","code":"{\n \"status\": \"off\"\n}","id":"996074fc-0efb-4bf8-8f16-95cbad2f5043","savedPayload":{"position":{"x":42914.59155513783,"y":32314.693016214136},"targetPosition":{"x":43087.94547795279,"y":32181.92277542006},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Product","name":"Strategies","id":"40c22092-c1c9-4a45-893a-b7372833df84"}}}],"id":"c49c4542-bc88-44c2-b537-6f2d5b3b3a3a","savedPayload":{"position":{"x":43087.94547795279,"y":32181.92277542006},"targetPosition":{"x":43182.81404257353,"y":31880.527527777554},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"frame":{"position":{"x":1320,"y":80},"width":150,"height":40,"radius":0}}},"id":"cfda3689-ce58-460a-81c3-4b99377e7184","savedPayload":{"position":{"x":44598.07907263305,"y":32221.458401450443},"targetPosition":{"x":44638.22490151728,"y":31620.194666293457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a4a8b171-8b07-4895-8c25-cedd8fa9c957","savedPayload":{"position":{"x":44638.52989266892,"y":31620.261680135452},"targetPosition":{"x":44068.49117132832,"y":31426.826447713953},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""},"frame":{"position":{"x":-1305,"y":-15},"width":240,"height":112.375,"radius":0}}},"id":"e6469c64-26cb-4a30-951d-a68147cf1ba5","savedPayload":{"position":{"x":44068.869578561484,"y":31427.048075342944},"targetPosition":{"x":42931.70615561109,"y":31041.063169142482},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"templateTarget":{"type":"Template Target","name":"New Template Target","id":"c01a6962-8b0e-4b06-8705-8751660aabbf","savedPayload":{"position":{"x":43452.64880473069,"y":31341.65552813608},"targetPosition":{"x":42931.70615561109,"y":31041.063169142482},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Dashboard","name":"Super Action Generated","id":"64fa2a31-21ea-4b56-a625-dc4082876a89"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateStructure.timeMachines === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Structure must have a child Time Machine.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Dashboard') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Dashboard.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n let templateClone = clone(templateScript.templateStructure.timeMachines)\n target.timeMachines.push(templateClone)\n setup(templateClone, target)\n\n return templateClone\n }\n}","id":"968fd18b-2a1b-41ca-9317-23f301607857","savedPayload":{"position":{"x":43383.721289869136,"y":31437.822140544642},"targetPosition":{"x":42931.70615561109,"y":31041.063169142482},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"41c01e94-6c5c-4b71-9c70-8d962c86fec0","savedPayload":{"position":{"x":42931.70615561109,"y":31041.063169142482},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"/*\nMaster Script\n*/\nfunction newNewMasterScriptInstance() {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction, masterScript) {\n /* Validations */\n if (superAction.payload.parentNode.type !== 'Market') {\n superAction.payload.uiObject.setErrorMessage('Super Action Parent Node needs to be of type Market.')\n return\n }\n if (superAction.payload.parentNode.baseAsset === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Node needs to have a child Market Base Asset.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Node needs to have a child Market Quoted Asset.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Base Asset Node needs to reference an Asset.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market Quoted Asset Node needs to reference an Asset.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Asset Node needs to be a child of Exchange Assets.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset.payload.referenceParent.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Asset Node needs to be a child of Exchange Assets.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Assets needs to be a child of an Exchange Node.')\n return\n }\n if (superAction.payload.parentNode.quotedAsset.payload.referenceParent.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Assets needs to be a child of an Exchange Node.')\n return\n }\n if (superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode.payload.parentNode.id !== superAction.payload.parentNode.quotedAsset.payload.referenceParent.payload.parentNode.payload.parentNode.id) {\n superAction.payload.uiObject.setErrorMessage('Assets must be from the same Exchange.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market needs to be a child of Exchange Markets.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Markets needs to be a child of an Exchange.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.id !== superAction.payload.parentNode.baseAsset.payload.referenceParent.payload.parentNode.payload.parentNode.id) {\n superAction.payload.uiObject.setErrorMessage('Assets and Market must be defined at the same Exchange.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange needs to have a child Exchange Accounts.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts.length === 0) {\n superAction.payload.uiObject.setErrorMessage('Exchange Accounts to needs have a child User Account.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[0].userKeys === undefined) {\n superAction.payload.uiObject.setErrorMessage('User Account needs to have a child User Keys.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode.exchangeAccounts.userAccounts[0].userKeys.keys.length === 0) {\n superAction.payload.uiObject.setErrorMessage('User Keys needs to have a child Exchange Account Key.')\n return\n }\n\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n if (templateScript.javascriptCode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Javascript Code.')\n return\n }\n if (templateScript.templateStructure === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Template Structure.')\n return\n }\n if (templateScript.templateTarget === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Template Target.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent.')\n return\n }\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n executionResult = templateScriptInstance.runValidations(superAction)\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n return true\n }\n\n function runScript(superAction, masterScript) {\n let market = superAction.payload.parentNode\n let exchange = superAction.payload.parentNode.payload.parentNode.payload.parentNode\n let key = exchange.exchangeAccounts.userAccounts[0].userKeys.keys[0]\n let singleMarketData\n let backSessionReference\n let paperSessionReference\n let liveSessionReference\n let ForwardSessionReference\n let dataMiningTaskManagers = []\n let testingEnvironmentTaskManager\n let productionEnvironmentTaskManager\n let timeMachine\n\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateClone\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n templateClone = templateScriptInstance.runScript(superAction, templateScript)\n }\n\n if (executionResult !== true) { return false }\n\n switch (templateClone.payload.parentNode.payload.parentNode.type) {\n case 'Session Independent Data': {\n singleMarketData = templateClone\n break\n }\n case 'Session Based Data': {\n switch (templateClone.templateType) {\n case 'Backtesting Session': {\n backSessionReference = templateClone\n break\n }\n case 'Paper Trading Session': {\n paperSessionReference = templateClone\n break\n }\n case 'Live Trading Session': {\n liveSessionReference = templateClone\n break\n }\n case 'Forward Testing Session': {\n ForwardSessionReference = templateClone\n break\n }\n }\n break\n }\n case 'Data Mining': {\n dataMiningTaskManagers.push(templateClone)\n break\n }\n case 'Testing Environment': {\n testingEnvironmentTaskManager = templateClone\n break\n }\n case 'Production Environment': {\n productionEnvironmentTaskManager = templateClone\n break\n }\n case 'Charting Space': {\n timeMachine = templateClone\n break\n }\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n /* Here we will find the sessions within the tasks in order to put them as a reference of the Session Reference nodes we already have */\n let session\n session = findSession('Backtesting Session', testingEnvironmentTaskManager)\n backSessionReference.payload.referenceParent = session\n session = findSession('Paper Trading Session', testingEnvironmentTaskManager)\n paperSessionReference.payload.referenceParent = session\n session = findSession('Live Trading Session', productionEnvironmentTaskManager)\n liveSessionReference.payload.referenceParent = session\n session = findSession('Forward Testing Session', productionEnvironmentTaskManager)\n ForwardSessionReference.payload.referenceParent = session\n\n function findSession(sessionType, taskManager) {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.session !== undefined) {\n if (process.session.type === sessionType) {\n return process.session\n }\n }\n }\n }\n }\n }\n }\n\n /* Here, we are going to connect the layers of the charts at the Time Machine */\n for (let i = 0; i < timeMachine.timelineCharts.length; i++) {\n let timelineChart = timeMachine.timelineCharts[i]\n switch (timelineChart.name) {\n case 'Indicators': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'Candles')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Bands')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 2: {\n let dataProduct = findDataProduct(singleMarketData, 'Resistances')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 3: {\n let dataProduct = findDataProduct(singleMarketData, 'Supports')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 4: {\n let dataProduct = findDataProduct(singleMarketData, 'Candles Channels')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 5: {\n let dataProduct = findDataProduct(singleMarketData, 'Candles Channels Objects')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 6: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Channels')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 7: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Channels Objects')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 8: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Sub-Channels')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 9: {\n let dataProduct = findDataProduct(singleMarketData, 'Bollinger Sub-Channels Objects')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 10: {\n let dataProduct = findDataProduct(singleMarketData, 'Popular SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 11: {\n let dataProduct = findDataProduct(singleMarketData, 'Base5 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 12: {\n let dataProduct = findDataProduct(singleMarketData, 'Base6 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 13: {\n let dataProduct = findDataProduct(singleMarketData, 'Base7 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 14: {\n let dataProduct = findDataProduct(singleMarketData, 'Base11 SMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 15: {\n let dataProduct = findDataProduct(singleMarketData, 'Popular EMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 16: {\n let dataProduct = findDataProduct(singleMarketData, 'Base7 EMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 17: {\n let dataProduct = findDataProduct(singleMarketData, 'Base11 EMAs')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'Volumes': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'Volumes')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'Volume-Stairs')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'Oscilators': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'Percentage Bandwidth')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'RSI14')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'MACD': {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 122609')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 031016')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 2: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 081709')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 3: {\n let dataProduct = findDataProduct(singleMarketData, 'MACD 245209')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n break\n }\n case 'Backtesting': {\n setupSessionReference(timelineChart, backSessionReference)\n break\n }\n case 'Paper Trading': {\n setupSessionReference(timelineChart, paperSessionReference)\n break\n }\n case 'Live Trading': {\n setupSessionReference(timelineChart, liveSessionReference)\n break\n }\n case 'Forward Testing': {\n setupSessionReference(timelineChart, ForwardSessionReference)\n break\n }\n }\n }\n\n function findDataProduct(singleMarketData, name) {\n for (let i = 0; i < singleMarketData.dataProducts.length; i++) {\n let dataProduct = singleMarketData.dataProducts[i]\n if (dataProduct.name === name) {\n return dataProduct\n }\n }\n }\n function setupSessionReference(timelineChart, sessionReference) {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n switch (j) {\n case 0: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Simulation')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 1: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Conditions')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 2: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Trades')\n layer.payload.referenceParent = dataProduct\n break\n }\n case 3: {\n let dataProduct = findDataProduct(sessionReference.singleMarketData, 'Strategies')\n layer.payload.referenceParent = dataProduct\n break\n }\n }\n layer.payload.uiObject.physics()\n }\n }\n\n let exchangeAndMarket = superAction.payload.parentNode.payload.parentNode.payload.parentNode.name + ' ' + market.name\n timeMachine.name = exchangeAndMarket\n\n for (let i = 0; i < dataMiningTaskManagers.length; i++) {\n let dataMiningTaskManager = dataMiningTaskManagers[i]\n dataMiningTaskManager.name = dataMiningTaskManager.name + ' ' + market.name\n }\n\n testingEnvironmentTaskManager.name = testingEnvironmentTaskManager.name + ' ' + market.name\n productionEnvironmentTaskManager.name = productionEnvironmentTaskManager.name + ' ' + market.name\n return true\n }\n}\n/*\nEnd Manster Script\n*/","id":"d234c201-4b50-48c6-be9d-f3b141c8cc16","savedPayload":{"position":{"x":42315.379156118935,"y":30864.883508780593},"targetPosition":{"x":41890.14038179374,"y":30439.527507516414},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"95b24e48-3b10-47cb-b149-8667571f4a83","savedPayload":{"position":{"x":41890.236139228655,"y":30439.426551049364},"targetPosition":{"x":40847.785430914875,"y":31041.241941751345},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"o"}}},{"type":"Master Script","name":"Delete Market Dependencies ","templateScripts":[{"type":"Template Script","name":"Delete Testing Environment Task Managers","templateTarget":{"type":"Template Target","name":"New Template Target","id":"a97aa286-3199-4362-900a-65b604bf5b9a","savedPayload":{"position":{"x":41729.477786552314,"y":31046.11236071952},"targetPosition":{"x":41582.54274899229,"y":31205.193628384397},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Testing Environment","name":"New Testing Environment","id":"2ae685a1-25c6-4bd5-b559-d83e373e8d45"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Testing Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeTasks.length; e++) {\n let exchangeTasks = target.exchangeTasks[e]\n if (exchangeTasks.payload.referenceParent !== undefined) {\n if (exchangeTasks.payload.referenceParent.id === exchange.id) {\n for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {\n let taskManager = exchangeTasks.taskManagers[k]\n checkReferenceToMarket()\n function checkReferenceToMarket() {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n if (process.marketReference.payload !== undefined) {\n if (process.marketReference.payload.referenceParent !== undefined) {\n if (process.marketReference.payload.referenceParent.id === market.id) {\n nodesToDelete.push(taskManager)\n return\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n if (exchangeTasks.taskManagers.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeTasks)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"9d020e32-68e5-4123-b0ad-cb443d778905","savedPayload":{"position":{"x":41785.524618173,"y":31129.822745374793},"targetPosition":{"x":41582.54274899229,"y":31205.193628384397},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"12eb790b-7f24-48bf-a4f0-34159f3d1863","savedPayload":{"position":{"x":41582.54274899229,"y":31205.193628384397},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Production Environment Task Managers","templateTarget":{"type":"Template Target","name":"New Template Target","id":"2ef5ba0f-10f7-4af0-81c1-2e89b9aea583","savedPayload":{"position":{"x":41853.66308982842,"y":31264.801386694602},"targetPosition":{"x":41656.82837164478,"y":31355.520360853872},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Production Environment","name":"New Production Environment","id":"32656457-f603-416a-b805-546642094dc4"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Production Environment') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeTasks.length; e++) {\n let exchangeTasks = target.exchangeTasks[e]\n if (exchangeTasks.payload.referenceParent !== undefined) {\n if (exchangeTasks.payload.referenceParent.id === exchange.id) {\n for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {\n let taskManager = exchangeTasks.taskManagers[k]\n checkReferenceToMarket()\n function checkReferenceToMarket() {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n if (process.marketReference.payload !== undefined) {\n if (process.marketReference.payload.referenceParent !== undefined) {\n if (process.marketReference.payload.referenceParent.id === market.id) {\n nodesToDelete.push(taskManager)\n return\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n if (exchangeTasks.taskManagers.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeTasks)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"9f9515a8-2e47-4a6f-acc3-6e4e01a610b2","savedPayload":{"position":{"x":41873.37022951505,"y":31363.719899709926},"targetPosition":{"x":41656.82837164478,"y":31355.520360853872},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"29b330a4-d4f6-45ac-8b41-090740dc418f","savedPayload":{"position":{"x":41656.82837164478,"y":31355.520360853872},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Data Mining Task Managers","templateTarget":{"type":"Template Target","name":"New Template Target","id":"c3417c9a-68b3-4d49-9514-0d2d30cea254","savedPayload":{"position":{"x":41884.48440596237,"y":31513.967037417144},"targetPosition":{"x":41667.84415563366,"y":31522.410064016763},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Data Mining","name":"New Data Mining","id":"7449a608-32ca-4285-94d1-c96791e2817f"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Data Mining') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Data Mining.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeTasks.length; e++) {\n let exchangeTasks = target.exchangeTasks[e]\n if (exchangeTasks.payload.referenceParent !== undefined) {\n if (exchangeTasks.payload.referenceParent.id === exchange.id) {\n for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {\n let taskManager = exchangeTasks.taskManagers[k]\n checkReferenceToMarket()\n function checkReferenceToMarket() {\n for (let i = 0; i < taskManager.tasks.length; i++) {\n let task = taskManager.tasks[i]\n if (task.bot !== undefined) {\n if (task.bot.processes !== undefined) {\n for (let j = 0; j < task.bot.processes.length; j++) {\n let process = task.bot.processes[j]\n if (process.marketReference !== undefined) {\n if (process.marketReference.payload !== undefined) {\n if (process.marketReference.payload.referenceParent !== undefined) {\n if (process.marketReference.payload.referenceParent.id === market.id) {\n nodesToDelete.push(taskManager)\n return\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n if (exchangeTasks.taskManagers.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeTasks)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"2d8e00e3-836a-426d-9f78-db3262bd7454","savedPayload":{"position":{"x":41864.71847634008,"y":31612.922947914863},"targetPosition":{"x":41667.84415563366,"y":31522.410064016763},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"e8e96f57-041e-4c0d-a2bf-5a8e444636b1","savedPayload":{"position":{"x":41667.84415563366,"y":31522.410064016763},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Dashboard Time Machines","templateTarget":{"type":"Template Target","name":"New Template Target","id":"1cbc9a0e-d00e-4506-9a33-5fbcb3ed8900","savedPayload":{"position":{"x":41818.05356628126,"y":31755.042846552373},"targetPosition":{"x":41614.02097354356,"y":31680.97605960732},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Dashboard","name":"Super Action Generated","id":"64fa2a31-21ea-4b56-a625-dc4082876a89"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Dashboard') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Dashboard.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let k = 0; k < target.timeMachines.length; k++) {\n let timeMachine = target.timeMachines[k]\n for (let i = 0; i < timeMachine.timelineCharts.length; i++) {\n let timelineChart = timeMachine.timelineCharts[i]\n if (timelineChart.layersManager !== undefined) {\n for (let j = 0; j < timelineChart.layersManager.layers.length; j++) {\n let layer = timelineChart.layersManager.layers[j]\n if (layer.payload.referenceParent !== undefined) {\n if (layer.payload.referenceParent.payload !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode.payload !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode.payload.referenceParent !== undefined) {\n if (layer.payload.referenceParent.payload.parentNode.payload.referenceParent.id === market.id) {\n \n nodesToDelete.push(timeMachine)\n \n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"cf8f6b69-70f8-4035-801d-276c1e2d6b5d","savedPayload":{"position":{"x":41760.499195398515,"y":31841.017373526123},"targetPosition":{"x":41614.02097354356,"y":31680.97605960732},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"c78e76fc-311b-4ac7-942f-af9629ea8b76","savedPayload":{"position":{"x":41614.02097354356,"y":31680.97605960732},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Single Market Data","templateTarget":{"type":"Template Target","name":"New Template Target","id":"2d36e591-3314-4a0c-b04d-6372e961959d","savedPayload":{"position":{"x":41662.60051045396,"y":31954.075050489493},"targetPosition":{"x":41503.55623477179,"y":31806.85723421229},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Independent Data","name":"New Session Independent Data","id":"bf04ccea-266d-4115-b50e-fefdf29b873a"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Independent Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Independent Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeDataProducts.length; e++) {\n let exchangeDataProducts = target.exchangeDataProducts[e]\n if (exchangeDataProducts.payload.referenceParent !== undefined) {\n if (exchangeDataProducts.payload.referenceParent.id === exchange.id) {\n for (let i = 0; i < exchangeDataProducts.singleMarketData.length; i++) {\n let singleMarketData = exchangeDataProducts.singleMarketData[i]\n if (singleMarketData.payload !== undefined) {\n if (singleMarketData.payload.referenceParent !== undefined) {\n if (singleMarketData.payload.referenceParent.id === market.id) {\n nodesToDelete.push(singleMarketData)\n }\n }\n }\n }\n if (exchangeDataProducts.singleMarketData.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeDataProducts)\n }\n }\n }\n }\n\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"168466d3-1446-4b45-a647-ad962d1d5f2c","savedPayload":{"position":{"x":41578.48746317018,"y":32010.157336419907},"targetPosition":{"x":41503.55623477179,"y":31806.85723421229},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"f23edd8e-b0f9-4531-9550-69cdb0616bcc","savedPayload":{"position":{"x":41503.55623477179,"y":31806.85723421229},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Template Script","name":"Delete Session References","templateTarget":{"type":"Template Target","name":"New Template Target","id":"4fe33613-3b67-4e0f-a4b4-6caa4e6dd163","savedPayload":{"position":{"x":41443.711470549744,"y":32077.739391028543},"targetPosition":{"x":41353.18735100165,"y":31880.891142726974},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false},"referenceParent":{"type":"Session Based Data","name":"New Session Based Data","id":"29ca14e5-dabe-470a-be64-ed7996bd1e55"}}},"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"templateScriptConstructor = function () {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction) {\n /* Validations */\n\n if (templateScript.templateTarget.payload.referenceParent.type !== 'Session Based Data') {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent of type Session Based Data.')\n return\n }\n return true\n }\n\n function runScript(superAction, templateScript) {\n let target = templateScript.templateTarget.payload.referenceParent\n\n let nodesToDelete = []\n for (let e = 0; e < target.exchangeSessions.length; e++) {\n let exchangeSessions = target.exchangeSessions[e]\n if (exchangeSessions.payload.referenceParent !== undefined) {\n if (exchangeSessions.payload.referenceParent.id === exchange.id) {\n for (let i = 0; i < exchangeSessions.sessionReferences.length; i++) {\n let sessionReference = exchangeSessions.sessionReferences[i]\n let singleMarketData = sessionReference.singleMarketData\n if (singleMarketData !== undefined) {\n if (singleMarketData.payload !== undefined) {\n if (singleMarketData.payload.referenceParent !== undefined) {\n if (singleMarketData.payload.referenceParent.id === market.id) {\n nodesToDelete.push(sessionReference)\n }\n }\n }\n }\n }\n if (exchangeSessions.sessionReferences.length === nodesToDelete.length) {\n nodesToDelete.push(exchangeSessions)\n }\n }\n }\n }\n for (let i = 0; i < nodesToDelete.length; i++) {\n deleteNode(nodesToDelete[i])\n }\n }\n}","id":"f2cc52fd-cea8-437f-aa7d-a4d1100bb92e","savedPayload":{"position":{"x":41344.40711731363,"y":32097.37996261368},"targetPosition":{"x":41353.18735100165,"y":31880.891142726974},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"f8ffadd5-8eda-4c4e-be1c-c49b98045b42","savedPayload":{"position":{"x":41353.18735100165,"y":31880.891142726974},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"/*\nMaster Script\n*/\nfunction newNewMasterScriptInstance() {\n let thisObject = {\n runValidations: runValidations,\n runScript: runScript\n }\n return thisObject\n\n function runValidations(superAction, masterScript) {\n /* Validations */\n if (superAction.payload.parentNode.type !== 'Market') {\n superAction.payload.uiObject.setErrorMessage('Super Action Parent Node needs to be of type Market.')\n return\n }\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n if (superAction.payload.parentNode.type !== 'Market') {\n superAction.payload.uiObject.setErrorMessage('Super Action Parent Node needs to be of type Market.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Market needs to be a child of Exchange Markets.')\n return\n }\n if (superAction.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Exchange Markets needs to be a child of an Exchange.')\n return\n }\n if (templateScript.javascriptCode === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Javascript Code.')\n return\n }\n if (templateScript.templateTarget === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Script must have a child Template Target.')\n return\n }\n if (templateScript.templateTarget.payload.referenceParent === undefined) {\n superAction.payload.uiObject.setErrorMessage('Template Target must have a Reference Parent.')\n return\n }\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n executionResult = templateScriptInstance.runValidations(superAction)\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n return true\n }\n\n function runScript(superAction, masterScript) {\n let market = superAction.payload.parentNode\n let exchange = superAction.payload.parentNode.payload.parentNode.payload.parentNode\n\n for (let i = 0; i < masterScript.templateScripts.length; i++) {\n let templateScript = masterScript.templateScripts[i]\n\n try {\n let templateScriptConstructor\n\n eval(templateScript.javascriptCode.code)\n\n let executionResult = true\n let templateClone\n let templateScriptInstance = templateScriptConstructor()\n\n if (executionResult === true) {\n templateClone = templateScriptInstance.runScript(superAction, templateScript)\n }\n\n if (executionResult !== true) { return false }\n } catch (err) {\n console.log(err.stack)\n superAction.payload.uiObject.setErrorMessage(err.message)\n return false\n }\n }\n\n return true\n }\n}\n/*\nEnd Manster Script\n*/","id":"a9538a7f-32dd-4068-8af6-0f8a83f6f3c7","savedPayload":{"position":{"x":41186.32467320293,"y":31891.49809390885},"targetPosition":{"x":41242.73749253987,"y":31466.604853288718},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"c25d0331-8ac0-4834-aa77-743487455d45","savedPayload":{"position":{"x":41890.23583929248,"y":31643.061431023485},"targetPosition":{"x":40847.785430914875,"y":31041.241941751345},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1dd2a75d-7d97-4994-8c95-b392d933b1c1","savedPayload":{"position":{"x":40847.785430914875,"y":31041.241941751345},"targetPosition":{"x":39643.041459281856,"y":31041.13836154292},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"S"}}},"id":"17b5ea06-62d8-4a79-b0ba-f9d3b997c17f","savedPayload":{"position":{"x":39643.041459281856,"y":31041.13836154292},"targetPosition":{"x":39643.041459281856,"y":31041.13836154292},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}} \ No newline at end of file diff --git a/TaskServer/Assistant.js b/TaskServer/Assistant.js deleted file mode 100644 index 9a98b1cbd..000000000 --- a/TaskServer/Assistant.js +++ /dev/null @@ -1,1127 +0,0 @@ -exports.newAssistant = function newAssistant(BOT, logger, UTILITIES) { - - /* - This module allows trading bots to execute actions on the exchange. - */ - - const MODULE_NAME = "Assistant"; - - let bot = BOT; - - let thisObject = { - dataDependencies: undefined, - initialize: initialize, - createOrder: createOrder, - getPositions: getPositions, - getBalance: getBalance, - getAvailableBalance: getAvailableBalance, - getInitialBalance: getInitialBalance, - getProfits: getProfits, - getCombinedProfits: getCombinedProfits, - getROI: getROI, - getMarketRate: getMarketRate, - getTicker: getTicker, - sendMessage: sendMessage, - rememberThis: rememberThis, - remindMeOf: remindMeOf, - truncDecimals: truncDecimals, - sendEmail: sendEmail, - addExtraData: addExtraData - }; - - let utilities = UTILITIES.newCloudUtilities(logger); - - let openOrders = []; // These are the open positions at the exchange at the account the bot is authorized to use. - let openPositions = []; // These are the open positions the bot knows it made by itself. - - let context; - let exchangeAPI; - - let marketRate; - let ticker; - - return thisObject; - - function initialize(pContext, pExchangeAPI, pDataDependencies, callBackFunction) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - /* Save local values. */ - - context = pContext; - exchangeAPI = pExchangeAPI; - thisObject.dataDependencies = pDataDependencies; - - switch (bot.startMode) { - - case 'Live': { - getMarketRateFromExchange(); - break; - } - - case 'Backtest': { - validateExchangeSyncronicity(); - break; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] initialize -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] initialize -> bot.startMode = " + bot.startMode); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function getMarketRateFromExchange() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getMarketRateFromExchange -> Entering function."); } - - exchangeAPI.getTicker(bot.market, onTicker); - - return; - - function onTicker(err, pTicker) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getMarketRateFromExchange -> onTicker -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getMarketRateFromExchange -> onTicker -> We could not get the Market Price now."); } - - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - ticker = pTicker; - marketRate = pTicker.last; - - context.newHistoryRecord.marketRate = marketRate; - - validateExchangeSyncronicity(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getMarketRateFromExchange -> onTicker -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getMarketRateFromExchange -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function validateExchangeSyncronicity() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> validateExchangeSyncronicity -> Entering function."); } - - /* Procedure to validate we are in sync with the exchange. */ - - getPositionsAtExchange(onDone); - - function onDone(err) { - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> validateExchangeSyncronicity -> onDone -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> validateExchangeSyncronicity -> onDone -> Execution finished well."); } - calculateProfits(); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> Retry Later. Requesting Execution Retry."); - callBackFunction(err); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - break; - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function calculateProfits() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> calculateProfits -> Entering function."); } - - /* Calculate Profits */ - - if (context.executionContext.initialBalance.baseAsset > 0) { - - context.executionContext.profits.baseAsset = (thisObject.truncDecimals(context.executionContext.balance.baseAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset); - context.executionContext.profits.baseAsset = thisObject.truncDecimals(context.executionContext.profits.baseAsset); - } - - if (context.executionContext.initialBalance.quotedAsset > 0) { - - context.executionContext.profits.quotedAsset = (thisObject.truncDecimals(context.executionContext.balance.quotedAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset); - context.executionContext.profits.quotedAsset = thisObject.truncDecimals(context.executionContext.profits.quotedAsset); - } - - context.newHistoryRecord.profitsBaseAsset = context.executionContext.profits.baseAsset; - context.newHistoryRecord.profitsQuotedAsset = context.executionContext.profits.quotedAsset; - - /* Calculate Combined Profits */ - - if (context.executionContext.initialBalance.baseAsset > 0) { - - let convertedAssetsB = (thisObject.truncDecimals(context.executionContext.balance.quotedAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset)) * marketRate; - convertedAssetsB = thisObject.truncDecimals(convertedAssetsB); - - context.executionContext.combinedProfits.baseAsset = (thisObject.truncDecimals(context.executionContext.balance.baseAsset) + convertedAssetsB - thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset) * 100; - context.executionContext.combinedProfits.baseAsset = thisObject.truncDecimals(context.executionContext.combinedProfits.baseAsset); - } - - if (context.executionContext.initialBalance.quotedAsset > 0) { - - let convertedAssetsA = (thisObject.truncDecimals(context.executionContext.balance.baseAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset)) / marketRate; - convertedAssetsA = thisObject.truncDecimals(convertedAssetsA); - - context.executionContext.combinedProfits.quotedAsset = (thisObject.truncDecimals(context.executionContext.balance.quotedAsset) + convertedAssetsA - thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset) * 100; - context.executionContext.combinedProfits.quotedAsset = thisObject.truncDecimals(context.executionContext.combinedProfits.quotedAsset); - } - - context.newHistoryRecord.combinedProfitsA = context.executionContext.combinedProfits.baseAsset; - context.newHistoryRecord.combinedProfitsB = context.executionContext.combinedProfits.quotedAsset; - - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> calculateProfits -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getPositionsAtExchange(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> Entering function."); } - - /* - - Here we grab all the positions at the exchange for the account we are using for trading. We will not asume all the positions - were made by this bot, but we need them all to later check if all the ones made by the bot are still there, were executed or - manually cancelled by the exchange account owner. - - */ - - switch (bot.startMode) { - - case "Live": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> Live Mode Detected."); } - exchangeAPI.getOpenOrders(bot.market, onResponse); - break; - } - - case "Backtest": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> Backtest Mode Detected."); } - let openOrders = []; // We simulate all positions were executed. - onResponse(global.DEFAULT_OK_RESPONSE, openOrders); - break; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> bot.startMode = " + bot.startMode); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function onResponse(err, pOpenOrders) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> onResponse -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> onResponse -> pOpenOrders = " + JSON.stringify(pOpenOrders)); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { // Everything went well, we have the information requested. - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> onResponse -> Execution finished well."); } - openOrders = pOpenOrders; - ordersExecutionCheck(callBack); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> onResponse -> Retry Later. Requesting Execution Retry."); - callBack(err); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> onResponse -> Operation Failed. Aborting the process."); - callBack(err); - return; - } - break; - } - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function ordersExecutionCheck(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> Entering function."); } - - /* - - Here we check that all the positions we know are still valid at the exchange. If they are not, we will try to take appropiate - actions. Reasons why the positions might not be there are: - - 1. The user / account owner closed the positions manually. - 2. The exchange for some reason closed the positions. In some exchanges positions have an expiration time. - 3. The orders were executed. - - Situations 1 and 2 are similar unexpected and we will stop the bot execution when we detect them. Number 3 is an - expected behaviour and we will take appropiate action. - - Also, the position might still be there but it might have been partially executed. To detect that we need to compare the - position amounts we have on file to the one we are receiving from the exchange. - - Before we begin, we have to remove all the orders that have been already closed at the last execution of the bot. - - */ - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> JSON.stringify(context.executionContext.positions) = " + JSON.stringify(context.executionContext.positions)); } - - let openPositions = []; - - for (let a = 0; a < context.executionContext.positions.length; a++) { - - if (context.executionContext.positions[a].status === "open") { - - openPositions.push(context.executionContext.positions[a]); - - } - } - - /* - - This removes all orders leaving only the not executed position. Consider that we do want executed positions to be recored on the file so as to - have a record of their execution. But at next execution these records must be deleted before the new Execution Context file is created and - this is what we do here. - - */ - - context.executionContext.positions = openPositions; - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> Removing executed positions."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> JSON.stringify(context.executionContext.positions) = " + JSON.stringify(context.executionContext.positions)); } - - /* Now we can start checking what happened at the exchange. */ - - let i = 0; - - controlLoop(); - - function loopBody() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> Entering function."); } - - let position = context.executionContext.positions[i]; - let exchangePosition; - - for (let j = 0; j < openOrders.length; j++) { - - if (position.id === openOrders[j].id) { - - exchangePosition = openOrders[j]; - positionFound(); - return; - } - } - - positionNotFound(); - return; - - function positionFound() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> positionFound -> Entering function."); } - - /* - We update the final amountA based on what we got from the exchange. - This is needed since the values could be different from what we calculate. - */ - position.amountA = exchangePosition.amountA; - - /* - We need to know if the order was partially executed. To know that, we compare the amounts on file with the ones - received from the exchange. If they are the same, then the order is intact. Otherwise, to confirm a partial execution, - we will request the associated trades from the exchange. - */ - - if (position.amountB === exchangePosition.amountB) { - - /* Position is still there, untouched. Nothing to do here. */ - - next(); - return; - - } else { - - getPositionTradesAtExchange(position.id, confirmOrderWasPartiallyExecuted); - return; - - } - } - - function positionNotFound() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> positionNotFound -> Entering function."); } - - /* Position not found: we need to know if the order was executed. */ - - getPositionTradesAtExchange(position.id, confirmOrderWasExecuted); - - } - - function getPositionTradesAtExchange(pOrderId, innerCallBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> pOrderId = " + pOrderId); } - - /* - - Given one position, we request all the associated trades to it. - - */ - - switch (bot.startMode) { - - case "Live": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Live Mode Detected."); } - exchangeAPI.getOrder(pOrderId, bot.market, onResponse); - return; - - } - - case "Backtest": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Backtest Mode Detected."); } - - let trades = []; - - /* We look for the position at the executionContext */ - - for (let i = 0; i < context.executionContext.positions.length; i++) { - - let thisOrder = context.executionContext.positions[i]; - - if (thisOrder.id === pOrderId) { - - let feeRate = 0.002; // Default backtesting fee simulation - - let trade = { - id: Math.trunc(Math.random(1) * 1000000), - type: thisOrder.type, - rate: thisOrder.rate.toString(), - amountA: thisObject.truncDecimals(thisOrder.amountA).toString(), - amountB: thisObject.truncDecimals(thisOrder.amountB).toString(), - fee: thisObject.truncDecimals(feeRate).toString(), - date: (new Date()).valueOf() - } - - trades.push(trade); - - let order = { - trades: trades - } - onResponse(global.DEFAULT_OK_RESPONSE, order); - - return; - } - } - - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Position not found at Executioin Context."); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> bot.startMode = " + bot.startMode); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function onResponse(err, order) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { // Everything went well, we have the information requested. - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Execution finished well."); } - innerCallBack(order); - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Retry Later. Requesting Execution Retry."); - callBack(global.DEFAULT_RETRY_RESPONSE); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Operation Failed. Aborting the process."); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - break; - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function confirmOrderWasExecuted(order) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> trades = " + JSON.stringify(trades)); } - - let trades = order.trades - /* - - To confirm everything is ok, we will add all the amounts on trades asociated to the order and - they must be equal to the one on file. Otherwise something very strange could have happened, - in which case we will halt the bot execution. - - */ - - let sumBaseAsset = 0; - let sumQuotedAsset = 0; - let sumRate = 0; - - for (let k = 0; k < trades.length; k++) { - let trade = trades[k]; - sumBaseAsset = sumBaseAsset + thisObject.truncDecimals(trade.amountA); - sumQuotedAsset = sumQuotedAsset + thisObject.truncDecimals(trade.amountB); - sumRate = sumRate + thisObject.truncDecimals(trade.rate); - } - - sumBaseAsset = thisObject.truncDecimals(sumBaseAsset); - sumQuotedAsset = thisObject.truncDecimals(sumQuotedAsset); - - if (position.amountB !== sumQuotedAsset) { - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> position.amountB = " + position.amountB); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> sumQuotedAsset = " + sumQuotedAsset); - - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> Cannot be confirmed that the order was executed. It must be manually cancelled by the user or cancelled by the exchange itself."); - logger.write(MODULE_NAME, "[HINT] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> If the process was abruptally cancelled and then restarted, it is possible that now is not sincronized with the exchange."); - logger.write(MODULE_NAME, "[HINT] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> In any case, to continue, you must manually delete the orders at the exchange."); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - - /* - - Confirmed that order was executed. Next thing to do is to remember the trades and change its status. - - */ - - position.status = "executed"; - - if (position.type === "sell") { - context.newHistoryRecord.sellExecRate = sumRate / trades.length; - } else { - context.newHistoryRecord.buyExecRate = sumRate / trades.length; - } - - applyTradesToContext(trades); - - let newTransaction = { - type: position.type + " executed", - position: position - }; - - context.executionContext.transactions.push(newTransaction); - - /* All done. */ - - next(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function confirmOrderWasPartiallyExecuted(trades) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> Entering function."); } - - let trades = order.trades - - /* - - To confirm everything is ok, we will add all the amounts on trades plus the remaining amounts - at the order at the exchange and they must be equal to the one on file. Otherwise an inconsistent - state has been detected, in which case we will halt the bot execution. - - */ - - let sumBaseAsset = 0; - let sumQuotedAsset = 0; - - for (let k = 0; k < trades.length; k++) { - let trade = trades[k]; - sumBaseAsset = sumBaseAsset + thisObject.truncDecimals(trade.amountA); - sumQuotedAsset = sumQuotedAsset + thisObject.truncDecimals(trade.amountB); - } - - /* To this we add the current position amounts. */ - - sumBaseAsset = sumBaseAsset + thisObject.truncDecimals(exchangePosition.amountA); - sumQuotedAsset = sumQuotedAsset + thisObject.truncDecimals(exchangePosition.amountB); - - sumBaseAsset = thisObject.truncDecimals(sumBaseAsset); - sumQuotedAsset = thisObject.truncDecimals(sumQuotedAsset); - - /* - * Next let's get the max decimal positions for the pair being trade returned by the exchange and validate that at least - * one decimal position less than the max matches. The reason for this is because there could be some - * rounding on the exchange when there are partial executed orders that will interfiere with the exact validation. - * - * IE: For Poloniex USDT/BTC the max returned decimal positions is 8, it means we will validate that the diference is less than 0.0000001 - */ - - let exchangeParam = exchangeAPI.getMaxDecimalPositions(); - let minValue = '0.' + (1).toPrecision(exchangeParam - 1).split('.').reverse().join(''); - let exchangePrecision = parseFloat(parseFloat(minValue).toFixed(exchangeParam)); - - if (Math.abs(position.amountA - sumBaseAsset) > exchangePrecision || Math.abs(position.amountB - sumQuotedAsset) > exchangePrecision) { - - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountA = " + position.amountA); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumBaseAsset = " + sumBaseAsset); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountB = " + position.amountB); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumQuotedAsset = " + sumQuotedAsset); - - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> Cannot confirm that a partial execution was done well."); - - /* There are diferences on the responses between the getPosition and getTrades that causes some issues, let's retry. */ - callBack(global.DEFAULT_RETRY_RESPONSE); - return; - } - - /* - - Confirmed that order was partially executed. Next thing to do is to remember the trades and the new position. - - */ - - position.amountA = exchangePosition.amountA; - position.amountB = exchangePosition.amountB; - position.date = (new Date(exchangePosition.date)).valueOf(); - - applyTradesToContext(trades); - - let newTransaction = { - type: position.type + " partially executed", - position: position - }; - - context.executionContext.transactions.push(newTransaction); - - /* All done. */ - - next(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function applyTradesToContext(trades) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> applyTradesToContext -> Entering function."); } - - /* Here we apply the trades that already happened at the exchange to the balance and available balance of the bot. We also calculate its profits. */ - - for (k = 0; k < trades.length; k++) { - - let trade = trades[k]; - - position.trades.push(trade); - - context.newHistoryRecord.newTrades++; - - /* Calculate Balances */ - - let baseAsset = 0; - let quotedAsset = 0; - - if (trade.type === 'buy') { - - /* - - The fee received at each trade is the factor by which if we multiply asset B, we get the amount of Asset B extracted as fees by the exchange. - This happens with each individual trade. Remeber that one order can potentially be executed with 1 to many trades. - - */ - - let feeAmount = thisObject.truncDecimals(Number(trade.fee) * Number(trade.amountB)); - - baseAsset = Number(trade.amountA); - quotedAsset = Number(trade.amountB) - feeAmount; - - context.executionContext.balance.baseAsset = thisObject.truncDecimals(context.executionContext.balance.baseAsset - baseAsset); - context.executionContext.balance.quotedAsset = thisObject.truncDecimals(context.executionContext.balance.quotedAsset + quotedAsset); - - context.executionContext.availableBalance.quotedAsset = thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset + quotedAsset); - - /* Not the available balance for asset A is not affected since it was already reduced when the order was placed. */ - - } - - if (trade.type === 'sell') { - - /* - - The fee received at each trade is the factor by which if we multiply asset A, we get the amount of Asset A extracted as fees by the exchange. - This happens with each individual trade. Remeber that one order can potentially be executed with 1 to many trades. - - */ - - let feeAmount = thisObject.truncDecimals(Number(trade.fee) * Number(trade.amountA)); - - baseAsset = Number(trade.amountA) - feeAmount; - quotedAsset = Number(trade.amountB); - - context.executionContext.balance.baseAsset = thisObject.truncDecimals(context.executionContext.balance.baseAsset + baseAsset); - context.executionContext.balance.quotedAsset = thisObject.truncDecimals(context.executionContext.balance.quotedAsset - quotedAsset); - - context.executionContext.availableBalance.baseAsset = thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset + baseAsset); - - /* Not the available balance for asset B is not affected since it was already reduced when the order was placed. */ - - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> applyTradesToContext -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function next() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> next -> Entering function."); } - - i++; - controlLoop(); - - } - - function controlLoop() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> controlLoop -> Entering function."); } - - if (i < context.executionContext.positions.length) { - - loopBody(); - - } else { - - final(); - - } - } - - function final() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> final -> Entering function."); } - - callBack(global.DEFAULT_OK_RESPONSE); - - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function createOrder(pType, pRate, pAmountA, pAmountB, callBackFunction) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> Entering function."); } - - /* Removing extra decimals. */ - - pRate = thisObject.truncDecimals(pRate); - pAmountA = thisObject.truncDecimals(pAmountA); - pAmountB = thisObject.truncDecimals(pAmountB); - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pType = " + pType); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pRate = " + pRate); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pAmountA = " + pAmountA); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pAmountB = " + pAmountB); } - - /* Validations that the limits are not surpassed. */ - - if (pType === 'buy') { - - if (pAmountA > thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset)) { - - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountA is grater than the Available Balance."); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountA = " + pAmountA); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> Available Balance = " + thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset)); - - let err = { - result: global.DEFAULT_FAIL_RESPONSE.result, - message: 'Not Enough Available Balance to Buy.' - }; - - callBackFunction(err); - return; - } - } - - if (pType === 'sell') { - - if (pAmountB > thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset)) { - - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountB is grater than the Available Balance."); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountB = " + pAmountB); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> Available Balance = " + thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset)); - - let err = { - result: global.DEFAULT_FAIL_RESPONSE.result, - message: 'Not Enough Available Balance to Sell.' - }; - - callBackFunction(err); - return; - } - } - - /* All validations passed, we proceed. */ - - switch (bot.startMode) { - - case "Live": { - - exchangeAPI.createOrder(bot.market, pType, pRate, pAmountA, pAmountB, onResponse); - return; - } - - case "Backtest": { - - if (pRate !== marketRate) { - - logger.write(MODULE_NAME, "[WARNING] createOrder -> Input Validations -> createOrder Rate can is different to marketRate while in Backtesting Mode. "); - //onResponse(global.DEFAULT_FAIL_RESPONSE, positionId); - } - - let positionId = Math.trunc(Math.random(1) * 1000000); - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> Simulating Exchange Response -> orderId = " + positionId); } - onResponse(global.DEFAULT_OK_RESPONSE, positionId); - return; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] createOrder -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] createOrder -> bot.startMode = " + bot.startMode); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function onResponse(err, order) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> onResponse -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> onResponse -> order.id = " + order.id); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { // Everything went well, we have the information requested. - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> onResponse -> Execution finished well."); } - - let position = { - id: order.id, - type: pType, - rate: pRate, - amountA: pAmountA, - amountB: pAmountB, - date: (bot.processDatetime.valueOf()), - status: "open", - trades: [] - }; - - context.executionContext.positions.push(position); - - let newTransaction = { - type: "newPosition", - position: position - }; - - context.executionContext.transactions.push(newTransaction); - - context.newHistoryRecord.newPositions++; - - recalculateRateAverages(); - - /* Recalculate Available Balances */ - - if (position.type === 'buy') { - - context.executionContext.availableBalance.baseAsset = thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset - pAmountA); - context.newHistoryRecord.lastBuyRate = pRate; - } - - if (position.type === 'sell') { - - context.executionContext.availableBalance.quotedAsset = thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset - pAmountB); - context.newHistoryRecord.lastSellRate = pRate; - } - - callBackFunction(global.DEFAULT_OK_RESPONSE, position); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] createOrder -> onResponse -> Retry Later. Requesting Execution Retry."); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] createOrder -> onResponse -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - break; - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] createOrder -> onResponse -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] createOrder -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getPublicTradeHistory(baseAsset, quotedAsset, startTime, endTime, callback) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTradeHistory -> Entering function."); } - - exchangeAPI.getPublicTradeHistory(baseAsset, quotedAsset, startTime, endTime, callback); - } - - function recalculateRateAverages() { - - /* This function calculates the weighted rates of buy and sell positions. This is a way to summarize with only one rate, where the positions are. */ - - let sumBuyWeightedRates = 0; - let sumSellWeightedRates = 0; - - let sumBuyWeights = 0; - let sumSellWeights = 0; - - for (let i = 0; i < context.executionContext.positions.length; i++) { - - let position = context.executionContext.positions[i]; - - if (position.type === 'buy') { - - sumBuyWeightedRates = sumBuyWeightedRates + position.rate * position.amountB - sumBuyWeights = sumBuyWeights + position.amountB; - - } - - if (position.type === 'sell') { - - sumSellWeightedRates = sumSellWeightedRates + position.rate * position.amountB - sumSellWeights = sumSellWeights + position.amountB; - - } - - } - - context.newHistoryRecord.buyAvgRate = (sumBuyWeightedRates / sumBuyWeights || 0); - context.newHistoryRecord.sellAvgRate = (sumSellWeightedRates / sumSellWeights || 0); - } - - function getPositions() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositions -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositions -> JSON.stringify(context.executionContext.positions) = " + JSON.stringify(context.executionContext.positions)); } - - return JSON.parse(JSON.stringify(context.executionContext.positions)); - } - - function getBalance() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getBalance -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getBalance -> JSON.stringify(context.executionContext.balance) = " + JSON.stringify(context.executionContext.balance)); } - - return JSON.parse(JSON.stringify(context.executionContext.balance)); - } - - function getAvailableBalance() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getAvailableBalance -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getAvailableBalance -> JSON.stringify(context.executionContext.availableBalance) = " + JSON.stringify(context.executionContext.availableBalance)); } - - return JSON.parse(JSON.stringify(context.executionContext.availableBalance)); - } - - function getInitialBalance() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getInitialBalance -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getInitialBalance -> JSON.stringify(context.executionContext.initialBalance) = " + JSON.stringify(context.executionContext.initialBalance)); } - - return JSON.parse(JSON.stringify(context.executionContext.initialBalance)); - } - - function getProfits() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getProfits -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getProfits -> JSON.stringify(context.executionContext.profits) = " + JSON.stringify(context.executionContext.profits)); } - - return JSON.parse(JSON.stringify(context.executionContext.profits)); - } - - function getCombinedProfits() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getCombinedProfits -> Entering function."); } - - let combinedProfits = { - baseAsset: context.newHistoryRecord.combinedProfitsA, - quotedAsset: context.newHistoryRecord.combinedProfitsB - } - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getCombinedProfits -> JSON.stringify(combinedProfits) = " + JSON.stringify(combinedProfits)); } - - return JSON.parse(JSON.stringify(combinedProfits)); - } - - function getROI() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getROI -> Entering function."); } - - let ROI = { - baseAsset: (context.executionContext.balance.baseAsset - context.executionContext.initialBalance.baseAsset) / context.executionContext.initialBalance.baseAsset * 100, - quotedAsset: (context.executionContext.balance.quotedAsset - context.executionContext.initialBalance.quotedAsset) / context.executionContext.initialBalance.quotedAsset * 100 - } - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getROI -> JSON.stringify(ROI) = " + JSON.stringify(ROI)); } - - return JSON.parse(JSON.stringify(ROI)); - } - - function getMarketRate() { - return marketRate; - } - - function getTicker() { - return ticker; - } - - function sendMessage(pRelevance, pTitle, pBody) { - let runIndex = context.statusReport.runs.length - 1; - - context.newHistoryRecord.messageRelevance = pRelevance; - context.newHistoryRecord.messageTitle = bot.startMode + "." + runIndex + ": " + pTitle; - context.newHistoryRecord.messageBody = pBody; - } - - function rememberThis(pKey, pValue) { - if (context.executionContext.remember === undefined) { - context.executionContext.remember = {}; - } - context.executionContext.remember[pKey] = pValue; - } - - function remindMeOf(pKey) { - if (context.executionContext.remember === undefined) { - context.executionContext.remember = {}; - } - return context.executionContext.remember[pKey]; - } - - function sendEmail(pTitle, pBody, pTo) { - logger.write(MODULE_NAME, "[WARN] sendEmail -> Send emails is currently disabled."); - return; - } - - function addExtraData(pExtraDataArray) { - context.extraData.push(pExtraDataArray); - } - - function truncDecimals(pFloatValue, pDecimals) { - if (!pDecimals) pDecimals = 8; // Default value - return parseFloat(parseFloat(pFloatValue).toFixed(pDecimals)); - } -}; diff --git a/TaskServer/Commons.js b/TaskServer/Commons.js index fcc9ded0b..f3b4cc853 100644 --- a/TaskServer/Commons.js +++ b/TaskServer/Commons.js @@ -2,8 +2,6 @@ const FULL_LOG = true; const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; - const ONE_MIN_IN_MILISECONDS = 60 * 1000; const MODULE_NAME = "Commons"; @@ -12,7 +10,7 @@ validateOutputDatasets: validateOutputDatasets, inflateDatafiles: inflateDatafiles, dataBuildingProcedure: dataBuildingProcedure, - calculationsProcedure: calculationsProcedure, + calculationsProcedure: calculationsProcedure, generateFileContent: generateFileContent, writeFile: writeFile }; @@ -25,15 +23,15 @@ function validateDataDependencies(dataDependencies, callBackFunction) { for (let i = 0; i < dataDependencies.length; i++) { - let dataDependencyNode = dataDependencies[i] + let dataDependencyNode = dataDependencies[i] /* Basic validations to see if we have everything we need. */ - if (dataDependencyNode.referenceParent.parentNode.code.singularVariableName === undefined) { + if (dataDependencyNode.referenceParent.parentNode.config.singularVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (dataDependencyNode.referenceParent.parentNode.code.pluralVariableName === undefined) { + if (dataDependencyNode.referenceParent.parentNode.config.pluralVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -61,12 +59,12 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDatasetNode.referenceParent.parentNode.code.singularVariableName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.config.singularVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDatasetNode.referenceParent.parentNode.code.pluralVariableName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.config.pluralVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -79,7 +77,7 @@ if (outputDatasetNode.referenceParent.parentNode.dataBuilding === undefined) { logger.write(MODULE_NAME, "[WARN] start -> Product Definition " + outputDatasetNode.referenceParent.parentNode.name + " without a Data Building Procedure. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); } - if (outputDatasetNode.referenceParent.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Dataset witn no codeName defined. Product Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -91,7 +89,7 @@ return } - if (outputDatasetNode.referenceParent.parentNode.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition witn no codeName defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -103,7 +101,7 @@ return } - if (outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -115,7 +113,7 @@ return } - if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -132,19 +130,19 @@ */ for (let i = 0; i < dataDependencies.length; i++) { - let dataFile + let dataFile let jsonData // Datafile converted into Json objects let inputData // Includes calculated properties let singularVariableName // name of the variable for this product let recordDefinition - let dataDependencyNode = dataDependencies[i] + let dataDependencyNode = dataDependencies[i] dataFile = dataFiles.get(dataDependencyNode.id) if (dataFile === undefined) { continue } // When a datafile is not found it might be because we are processing market or daily and at the dependency array there are both types mixed up. recordDefinition = dataDependencyNode.referenceParent.parentNode.record - singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName + singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName /* Transform the raw data into JSON objects */ jsonData = jsonifyDataFile(dataFile, recordDefinition) /* Add the calculated properties */ @@ -153,7 +151,7 @@ } else { inputData = jsonData } - products[dataDependencyNode.referenceParent.parentNode.code.pluralVariableName] = inputData + products[dataDependencyNode.referenceParent.parentNode.config.pluralVariableName] = inputData /* The main dependency is defined as the first dependency processed. */ if (mainDependency.records === undefined) { @@ -177,8 +175,8 @@ let record = {} for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { - record[property.code.codeName] = dataFile[i][j] + if (property.config.isCalculated !== true) { + record[property.config.codeName] = dataFile[i][j] } } @@ -199,7 +197,7 @@ let system = { // These are the available system variables to be used in User Code and Formulas timeFrame: timeFrame, - ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS + ONE_DAY_IN_MILISECONDS: global.ONE_DAY_IN_MILISECONDS } let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. let results = [] @@ -238,13 +236,13 @@ /* For each calculated property we apply its formula*/ for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated === true) { + if (property.config.isCalculated === true) { if (property.formula !== undefined) { if (property.formula.code !== undefined) { try { let newValue = eval(property.formula.code) let currentRecord = product[variableName] - currentRecord[property.code.codeName] = newValue + currentRecord[property.config.codeName] = newValue } catch (err) { logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) @@ -277,7 +275,7 @@ interExecutionMemory, processingDailyFiles, currentDay - ) { + ) { /* This function has as an input the products object, with all the information @@ -286,12 +284,12 @@ a new set of information. */ - let lastInstantOfTheDay + let lastInstantOfTheDay let yesterday = {} let system = { // These are the available system variables to be used in User Code and Formulas timeFrame: timeFrame, - ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS, - ONE_MIN_IN_MILISECONDS: ONE_MIN_IN_MILISECONDS + ONE_DAY_IN_MILISECONDS: global.ONE_DAY_IN_MILISECONDS, + ONE_MIN_IN_MILISECONDS: global.ONE_MIN_IN_MILISECONDS } let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. let results = [] @@ -313,7 +311,7 @@ let dataDependencyMap = new Map() for (let j = 0; j < dataDependecyArray.length; j++) { let record = dataDependecyArray[j] - let key = record.begin.toString() + '-' + record.end.toString() + let key = record.begin.toString() + '-' + record.end.toString() dataDependencyMap.set(key, record) } dataDependencies[dataDependencyName] = dataDependencyMap @@ -321,7 +319,7 @@ } function getElement(dependencyName, currentRecordPrimaryDataDependency) { - let key = currentRecordPrimaryDataDependency.begin.toString() + '-' + currentRecordPrimaryDataDependency.end.toString() + let key = currentRecordPrimaryDataDependency.begin.toString() + '-' + currentRecordPrimaryDataDependency.end.toString() return dataDependencies[dependencyName].get(key) } @@ -340,7 +338,7 @@ if (processingDailyFiles) { /* Initialization of Last Instance */ - lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1; + lastInstantOfTheDay = currentDay.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1; if (interExecutionMemory[productName] === undefined) { /* The first time the intialization variables goes to the Inter Execution Memory. */ @@ -349,7 +347,7 @@ } else { /* We override the initialization, since the valid stuff is already at the Inter Execution Memory */ - variable = JSON.parse(JSON.stringify(interExecutionMemory[productName].variable)) + variable = JSON.parse(JSON.stringify(interExecutionMemory[productName].variable)) } } @@ -387,13 +385,13 @@ /* For each non-calculated property we apply its formula*/ for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { + if (property.config.isCalculated !== true) { if (property.formula !== undefined) { if (property.formula.code !== undefined) { try { let newValue = eval(property.formula.code) let currentRecord = product[variableName] - currentRecord[property.code.codeName] = newValue + currentRecord[property.config.codeName] = newValue } catch (err) { logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) @@ -411,7 +409,7 @@ results.push(product[variableName]); } - /* While we are positioned at Yesterday, we keey updating this data structure. */ + /* While we are positioned at Yesterday, we keep updating this data structure. */ if (processingDailyFiles) { if (positionedAtYesterday) { yesterday.variable = JSON.parse(JSON.stringify(variable)) @@ -457,7 +455,7 @@ at the next day, in whole, even if it starts in the previous day. */ - let lastInstantOdDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1; + let lastInstantOdDay = currentDay.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1; if (record.end < currentDay.valueOf() - 1) { continue; } if (record.end === lastInstantOdDay) { continue; } @@ -474,13 +472,13 @@ let propertySeparator = "" for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { + if (property.config.isCalculated !== true) { fileContent = fileContent + propertySeparator - if (property.code.isString === true) { + if (property.config.isString === true) { fileContent = fileContent + '"' } - fileContent = fileContent + record[property.code.codeName] - if (property.code.isString === true) { + fileContent = fileContent + record[property.config.codeName] + if (property.config.isString === true) { fileContent = fileContent + '"' } if (propertySeparator === "") { propertySeparator = ","; } diff --git a/TaskServer/Context.js b/TaskServer/Context.js deleted file mode 100644 index bc347977b..000000000 --- a/TaskServer/Context.js +++ /dev/null @@ -1,551 +0,0 @@ -exports.newContext = function newContext(BOT, logger, UTILITIES) { - - let bot = BOT; - - const MODULE_NAME = "Context"; - - /* - - This module deals with keeping the context between different executions of the bot. Conceptually this context is divided in 3 main parts: - - I. The non-business information which helps the mechanisms of giving continuety to the bots execution over time. This information goes in a file - called the Status Report, and there is a module, child of this one called Status Report that deals with loading and saving the contents of that file. - - II. The business information around the bot execution, including its balance, positions at exchanges, trades executed and many other things. These - are platform data mantained on behalf of the executing bot. All these is kept in one file per execution called the Execution Context. The collection - of all these files can later reconstruct the vital business information the bot had at each moment in time. - - II. Execution of any bot cannot be guaranteed that will be on fixed interval of times. We need an index of all executions that points to the detailed files - with all the business informatino the bot had each time it executed. We call this index the Execution History and there is one file to store it. - - */ - - let thisObject = { - statusReport: undefined, // Here is the information that defines which was the last sucessfull execution and some other details. - executionHistory: [], // This is the record of bot execution. - executionContext: undefined, // Here is the business information of the last execution of this bot process. - newHistoryRecord : { - date: undefined, - buyAvgRate: 0, - sellAvgRate: 0, - lastSellRate: 0, - sellExecRate: 0, - lastBuyRate: 0, - buyExecRate: 0, - marketRate: 0, - newPositions: 0, - newTrades: 0, - movedPositions: 0, - profitsBaseAsset: 0, - profitsQuotedAsset: 0, - combinedProfitsA: 0, - combinedProfitsB: 0, - messageRelevance: 0, - messageTitle: "", - messageBody: "" - }, - initialize: initialize, - saveThemAll: saveThemAll, - extraData: [] - }; - - /* - - During the process we will create a new History Record. This will go to the Context History file which essentially mantains an - index of all the bots executions. This file will later be plotted by the bot s plotter on the timeline, allowing end users to - know where there is information related to the actions taken by the bot. - - */ - - let statusReportModule; - - /* Utilities needed. */ - - let utilities = UTILITIES.newCloudUtilities(logger); - - /* Storage account to be used here. */ - - const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage = FILE_STORAGE.newFileStorage(logger); - - let statusDependencies; - - let runIndex; // This is the index for this run and depends on the startMode. - let sessionPath = '' - - return thisObject; - - function initialize(pStatusDependencies, callBackFunction) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - statusDependencies = pStatusDependencies; - - if (bot.SESSION !== undefined) { - sessionPath = bot.SESSION.folderName + "/" - } - - /* - Here we get the positions the bot did and that are recorded at the bot storage account. We will use them through out the rest - of the process. - */ - thisObject.newHistoryRecord.date = bot.processDatetime; - - getStatusReport(onDone); - - function onDone(err) { - try { - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> onDone -> Execution finished well."); } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> Retry Later. Requesting Execution Retry."); - callBackFunction(err); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> Operation Failed with custom response."); - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> err = " + JSON.stringify(err)); - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getStatusReport(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getStatusReport -> Entering function."); } - - let key = bot.dataMine + "-" + bot.codeName + "-" + "Context" + "-" + bot.dataSetVersion; - - statusReportModule = statusDependencies.statusReports.get(key); - thisObject.statusReport = statusReportModule.file; - - /* The context is created in 2 situations: 1) when at the bot config is set not to continue using the same context after a new execution. 2) when the status report does not exist. */ - - if (bot.hasTheBotJustStarted === true || thisObject.statusReport.runs === undefined) { - - createConext(callBack); - - } else { - - runIndex = thisObject.statusReport.runs.length - 1; - - getExecutionHistory(callBack); - - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getStatusReport -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getExecutionHistory(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionHistory -> Entering function."); } - - let fileName = "Execution.History." + bot.startMode + "." + runIndex + ".json"; - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + fileName; - - fileStorage.getTextFile(filePath, onFileReceived); - - function onFileReceived(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionHistory -> onFileReceived -> err = "+ JSON.stringify(err)); - callBack(err); - return; - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { - logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionHistory -> onFileReceived -> Content received = " + text); - } - - try { - - thisObject.executionHistory = JSON.parse(text); - - /* Move some values to the new record, in case there are no transactions that re-calculate them. */ - - thisObject.newHistoryRecord.buyAvgRate = thisObject.executionHistory[thisObject.executionHistory.length-1][1]; - thisObject.newHistoryRecord.sellAvgRate = thisObject.executionHistory[thisObject.executionHistory.length - 1][2]; - - getExecutionContext(callBack); - - } catch (err) { - - /* - - It might happen that the file content is corrupt or it does not exist. The bot can not run without an Execution Hitory, - since it is risky to ignore its own history unless it is its first execution in this mode ever. - - */ - - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionHistory -> onFileReceived -> Bot cannot execute without the Execution History. -> Err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionHistory -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getExecutionContext(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionContext -> Entering function."); } - - let date; - - date = new Date(thisObject.statusReport.runs[runIndex].lastExecution); - thisObject.statusReport.runs[runIndex].lastExecution = bot.processDatetime; - thisObject.statusReport.runs[runIndex].endDatetime = bot.processDatetime; - - let fileName = "/Execution.Context." + bot.startMode + "." + runIndex + ".json"; - let dateForPath = date.getUTCFullYear() + '/' + utilities.pad(date.getUTCMonth() + 1, 2) + '/' + utilities.pad(date.getUTCDate(), 2) + '/' + utilities.pad(date.getUTCHours(), 2) + '/' + utilities.pad(date.getUTCMinutes(), 2); - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + dateForPath + fileName; - - fileStorage.getTextFile(filePath, onFileReceived); - - function onFileReceived(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> onFileReceived -> err = "+ err.stack); - callBack(err); - return; - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { - logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionContext -> onFileReceived -> Content received = " + text); - } - - try { - - thisObject.executionContext = JSON.parse(text); - thisObject.executionContext.transactions = []; // We record here the transactions that happened duting this execution. - - /* Update the new History Record, in case there are no trades during this execution that updates it. */ - - thisObject.newHistoryRecord.profitsBaseAsset = thisObject.executionContext.profits.baseAsset; - thisObject.newHistoryRecord.profitsQuotedAsset = thisObject.executionContext.profits.quotedAsset; - - thisObject.newHistoryRecord.combinedProfitsA = thisObject.executionContext.combinedProfits.baseAsset; - thisObject.newHistoryRecord.combinedProfitsB = thisObject.executionContext.combinedProfits.quotedAsset; - - callBack(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - - /* - - It might happen that the file content is corrupt or it does not exist. The bot can not run without a Status Report, - since it is risky to ignore its own execution history. Except when the bot is running for the fist time in this mode, - an execution context file with the right format is needed. - - */ - - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> onFileReceived -> Response from storage. -> Text = " + text); - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> onFileReceived -> Bot cannot execute without the Execution Context. -> Err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function createConext(callBack) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> createConext -> Entering function."); } - /* - - When the bot is executed for the very first time, there are a few files that do not exist and need to be created, and that - content is what we are going to set up now. - - */ - - if (thisObject.statusReport.runs === undefined) { // This means that the Status Report does not exist. - - thisObject.statusReport.runs = []; - } - - let runContent = { - beginDatetime: bot.processDatetime, - endDatetime: bot.processDatetime, - lastExecution: bot.processDatetime - }; - - thisObject.statusReport.runs.push(runContent); - runIndex = thisObject.statusReport.runs.length - 1; - - thisObject.executionHistory = []; - - if (bot.VALUES_TO_USE.initialBalanceA === undefined || bot.VALUES_TO_USE.initialBalanceB === undefined) { throw new Error("Global Variables INITIAL_BALANCE_A and INITIAL_BALANCE_B cannot be undefined.") } - - const INITIAL_BALANCE_A = Number(bot.VALUES_TO_USE.initialBalanceB); // NOTE THAT THIS IS INVERTED BECAUSE OF POLONIEX THAT IS THE ONLY EXCHANGE SUPPORTED RIGHT NOW - const INITIAL_BALANCE_B = Number(bot.VALUES_TO_USE.initialBalanceA); - - - thisObject.executionContext = { - initialBalance: { // This is used to calculate profits. - baseAsset: INITIAL_BALANCE_A, - quotedAsset: INITIAL_BALANCE_B - }, - balance: { // This is the total balance that includes positions at the order book + funds available to be traded. - baseAsset: INITIAL_BALANCE_A, - quotedAsset: INITIAL_BALANCE_B // It starts with the initial initialBalance. - }, - availableBalance: { // This is the balance the bot has at any moment in time available to be traded (not in positions at the order book). - baseAsset: INITIAL_BALANCE_A, - quotedAsset: INITIAL_BALANCE_B // It starts with the initial initialBalance. - }, - profits: { - baseAsset: 0, - quotedAsset: 0 - }, - combinedProfits: { - baseAsset: 0, - quotedAsset: 0 - }, - positions: [], - transactions: [] - }; - - callBack(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> createConext -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function saveThemAll(callBackFunction) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> Entering function."); } - - writeExecutionContext(onDone); - - function onDone(err) { - try { - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> onDone -> Execution finished well."); } - - bot.hasTheBotJustStarted = false; - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> onDone -> Retry Later. Requesting Execution Retry."); - callBackFunction(err); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> onDone -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - break; - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> onDone -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeExecutionContext(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExecutionContext -> Entering function."); } - - let fileName = "/Execution.Context." + bot.startMode + "." + runIndex +".json"; - let dateForPath = bot.processDatetime.getUTCFullYear() + '/' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.processDatetime.getUTCDate(), 2) + '/' + utilities.pad(bot.processDatetime.getUTCHours(), 2) + '/' + utilities.pad(bot.processDatetime.getUTCMinutes(), 2); - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + dateForPath + fileName; - let fileContent = JSON.stringify(thisObject.executionContext); - - if(fileContent === undefined){ - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExecutionContext -> executionContext is undefined."); - callBackFunction(global.DEFAULT_ERROR_RESPONSE); - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExecutionContext -> fileContent = " + fileContent); } - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExecutionContext -> onFileCreated -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExecutionContext -> onFileCreated -> err = "+ err.stack); - callBack(err); - return; - } - - writeExucutionHistory(callBack); - return; - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExecutionContext -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeExucutionHistory(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> Entering function."); } - - let fileName = "Execution.History." + bot.startMode + "." + runIndex + ".json"; - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + fileName; - - let newRecord = [ - thisObject.newHistoryRecord.date.valueOf(), - thisObject.newHistoryRecord.buyAvgRate, - thisObject.newHistoryRecord.sellAvgRate, - - thisObject.newHistoryRecord.lastSellRate, - thisObject.newHistoryRecord.sellExecRate, - thisObject.newHistoryRecord.lastBuyRate, - thisObject.newHistoryRecord.buyExecRate, - - thisObject.newHistoryRecord.marketRate, - thisObject.newHistoryRecord.newPositions, - thisObject.newHistoryRecord.newTrades, - thisObject.newHistoryRecord.movedPositions, - thisObject.newHistoryRecord.profitsBaseAsset, - thisObject.newHistoryRecord.profitsQuotedAsset, - thisObject.newHistoryRecord.combinedProfitsA, - thisObject.newHistoryRecord.combinedProfitsB, - - thisObject.newHistoryRecord.messageRelevance, - thisObject.newHistoryRecord.messageTitle, - thisObject.newHistoryRecord.messageBody, - thisObject.extraData - ]; - - thisObject.executionHistory.push(newRecord); - - let fileContent = JSON.stringify(thisObject.executionHistory); - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> onFileCreated -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExucutionHistory -> onFileCreated -> err = "+ err.stack); - callBack(err); - return; - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { - logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> onFileCreated -> Content written = " + fileContent); - } - - /* Here we will write the file containing the max sequence number. */ - - fileContent = runIndex; - fileName = "Execution.History." + bot.startMode + "." + "Sequence" + ".json"; - filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + fileName; - - fileStorage.createTextFile(filePath, fileContent + '\n', onSequenceFileCreated); - - function onSequenceFileCreated(err) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> onFileCreated -> onSequenceFileCreated -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExucutionHistory -> onFileCreated -> onSequenceFileCreated -> err = "+ err.stack); - callBack(err); - return; - } - - writeStatusReport(callBack); - return; - } - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExucutionHistory -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeStatusReport(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeStatusReport -> Entering function."); } - - statusReportModule.file = thisObject.statusReport; - statusReportModule.save(callBack); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeStatusReport -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function truncDecimals(pFloatValue, pDecimals) { - - if (!pDecimals) pDecimals = 8; // Default value - - return parseFloat(parseFloat(pFloatValue).toFixed(pDecimals)); - - } - -}; diff --git a/TaskServer/DataSet.js b/TaskServer/DataSet.js index aa0c6bb1c..bb7f789e3 100644 --- a/TaskServer/DataSet.js +++ b/TaskServer/DataSet.js @@ -16,7 +16,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { /* Storage account to be used here. */ const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage + let fileStorage return thisObject; @@ -36,7 +36,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.code.codeName === undefined) { + if (thisObject.node.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Dataset witn no codeName defined -> Product Dataset = " + JSON.stringify(thisObject.node)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -48,7 +48,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.parentNode.code.codeName === undefined) { + if (thisObject.node.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Product Definition witn no codeName defined -> Product Definition = " + JSON.stringify(thisObject.node.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -60,7 +60,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.parentNode.parentNode.code.codeName === undefined) { + if (thisObject.node.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Bot witn no codeName defined. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -72,14 +72,14 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.parentNode.parentNode.parentNode.code.codeName === undefined) { + if (thisObject.node.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(thisObject.node.parentNode.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } /* Now we will see where do we need to fetch this data from. */ - let network = global.TASK_NETWORK + let network = global.TASK_NETWORK let datasetProductDefinition = thisObject.node.parentNode for (let i = 0; i < network.networkNodes.length; i++) { @@ -102,9 +102,9 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (datasetProductDefinition.id === productDefinition.id) { /* We found where the data is located on the network. */ - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.code.host, networkNode.code.webPort); + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); callBackFunction(global.DEFAULT_OK_RESPONSE); return } @@ -133,9 +133,9 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (datasetProductDefinition.id === productDefinition.id) { /* We found where the data is located on the network. */ - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.code.host, networkNode.code.webPort); + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); callBackFunction(global.DEFAULT_OK_RESPONSE); return } @@ -155,7 +155,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { callBackFunction(global.DEFAULT_FAIL_RESPONSE); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -173,8 +173,8 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTextFile -> Entering function."); } - let dataMineCodeName = thisObject.node.parentNode.parentNode.parentNode.code.codeName - let botCodeName = thisObject.node.parentNode.parentNode.code.codeName + let dataMineCodeName = thisObject.node.parentNode.parentNode.parentNode.config.codeName + let botCodeName = thisObject.node.parentNode.parentNode.config.codeName let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + dataMineCodeName + "/" + botCodeName; let filePath = filePathRoot + "/Output/" + pFolderPath; @@ -191,7 +191,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] 'getTextFile' -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] 'getTextFile' -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -204,8 +204,8 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createTextFile -> pFolderPath = " + pFolderPath); } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createTextFile -> pFileName = " + pFileName); } - let ownerId = thisObject.node.dataMine + "-" + thisObject.node.bot + "-" + thisObject.node.botVersion.major + "-" + thisObject.node.botVersion.minor + "-" + thisObject.node.dataSetVersion; - let botId = bot.dataMine + "-" + bot.codeName + "-" + bot.version.major + "-" + bot.version.minor + "-" + bot.dataSetVersion; + let ownerId = thisObject.node.dataMine + "-" + thisObject.node.bot + let botId = bot.dataMine + "-" + bot.codeName if (ownerId !== botId) { @@ -232,7 +232,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] 'createTextFile' -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] 'createTextFile' -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/TaskServer/DebugLog.js b/TaskServer/DebugLog.js index bcaf567a3..e7c874e9b 100644 --- a/TaskServer/DebugLog.js +++ b/TaskServer/DebugLog.js @@ -1,6 +1,5 @@ exports.newDebugLog = function newDebugLog() { - const ROOT_DIR = './'; const MODULE_NAME = "DebugLog"; let executionDatetime = "D." + global.EXECUTION_DATETIME.getUTCFullYear() + @@ -34,7 +33,7 @@ exports.newDebugLog = function newDebugLog() { disableLogging = pdisableLogging } catch (err) { - console.log("[ERROR] Debug Log -> initialize -> err = "+ err.stack); + console.log("[ERROR] Debug Log -> initialize -> err = " + err.stack); } } @@ -53,12 +52,12 @@ exports.newDebugLog = function newDebugLog() { date = date.getUTCFullYear() + '-' + strPad(date.getUTCMonth() + 1, 2, "0") + '-' + strPad(date.getUTCDate(), 2, "0"); console.log(new Date().toISOString() + " " + strPad(thisObject.bot.exchange, 20) + " " + strPad(thisObject.bot.market.baseAsset + '/' + thisObject.bot.market.quotedAsset, 10) + " " + strPad(pBot, 30) + " " + strPad(pProcess, 30) - + " Internal Loop # " + strPad(internalLoopCounter + 1, 8) + " " + strPad(date, 30) + " " + strPad(percentage, 10)) + + " Internal Loop # " + strPad(internalLoopCounter + 1, 8) + " " + strPad(date, 30) + " " + strPad(percentage, 10)) persist(); function strPad(str, max, fill) { - if (fill === undefined) {fill = " "} + if (fill === undefined) { fill = " " } str = str.toString(); return str.length < max ? strPad(fill + str, max) : str; } @@ -69,7 +68,7 @@ exports.newDebugLog = function newDebugLog() { /* Here we actually write the content of the in-memory log to a file */ try { - if (accumulatedLog === "[") {return} // nothing to persist at the moment. + if (accumulatedLog === "[") { return } // nothing to persist at the moment. internalLoopCounter++; @@ -82,7 +81,7 @@ exports.newDebugLog = function newDebugLog() { let filePath = thisObject.bot.filePathRoot + "/Logs/" + thisObject.bot.process + "/" if (thisObject.bot.SESSION !== undefined) { - filePath = filePath + thisObject.bot.SESSION.folderName + "/" + executionDatetime; + filePath = filePath + thisObject.bot.SESSION.folderName + "/" + executionDatetime; } else { filePath = filePath + executionDatetime; } @@ -102,32 +101,32 @@ exports.newDebugLog = function newDebugLog() { writeLog(); /* This is the implementation of the mechanism to auto-mantain logs. */ - if (contentToPersist.indexOf('[ERROR]') < 0) { + if (contentToPersist.indexOf('[ERROR]') < 0 && contentToPersist.indexOf('[PERSIST]') < 0) { thisObject.bot.LOGS_TO_DELETE_QUEUE.push(filePath + '/' + fileName) } if (thisObject.bot.LOGS_TO_DELETE_QUEUE.length > thisObject.bot.DELETE_QUEUE_SIZE) { let fileToDelete = thisObject.bot.LOGS_TO_DELETE_QUEUE[0] thisObject.bot.LOGS_TO_DELETE_QUEUE.splice(0, 1) /* Will delete this file only if it does not contains ERROR inside. */ - let fileContent = fileStorage.getTextFile(fileToDelete, onGetFile, true) + let fileContent = fileStorage.getTextFile(fileToDelete, onGetFile, true) function onGetFile(err, fileContent) { if (err.result === global.DEFAULT_OK_RESPONSE.result) { if (fileContent.indexOf("ERROR") < 0) { /* Logs will only be deleted when they contain no ERROR in them. */ fileStorage.deleteTextFile(fileToDelete); - } + } } else { fileStorage.deleteTextFile(fileToDelete); } } } - + function writeLog() { - fileStorage.createTextFile(filePath + '/' + fileName, contentToPersist + '\r\n' + "]", onFileCreated); + fileStorage.createTextFile(filePath + '/' + fileName, contentToPersist + '\r\n' + "]", onFileCreated, undefined, true); function onFileCreated(err) { if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - console.log("[ERROR] DebugLog -> persist -> onInizialized -> onFileCreated -> err = "+ err.message); + console.log("[ERROR] DebugLog -> persist -> onInizialized -> onFileCreated -> err = " + err.message); setTimeout(writeLog, 10000); // Lets retry until we make it. return; } @@ -135,16 +134,14 @@ exports.newDebugLog = function newDebugLog() { } } } catch (err) { - console.log("[ERROR] DebugLog -> persist -> err = "+ err.stack); + console.log("[ERROR] DebugLog -> persist -> err = " + err.stack); console.log("[ERROR] DebugLog -> persist -> onInizialized -> contentToPersist = " + contentToPersist); } } function write(pModule, pMessage) { - try { - if (disableLogging === true) { return; } if (thisObject.bot === undefined) { return; } @@ -174,7 +171,7 @@ exports.newDebugLog = function newDebugLog() { accumulatedLog = accumulatedLog + logLine; } catch (err) { - console.log("[ERROR] DebugLog -> write -> err = "+ err.stack); + console.log("[ERROR] DebugLog -> write -> err = " + err.stack); } } diff --git a/TaskServer/ExchangeAPI.js b/TaskServer/ExchangeAPI.js index 54c3c52e7..617b5a5a8 100644 --- a/TaskServer/ExchangeAPI.js +++ b/TaskServer/ExchangeAPI.js @@ -1,5 +1,5 @@ exports.newExchangeAPI = function newExchangeAPI(logger, BOT) { - + let MODULE_NAME = "Exchange API"; let LOG_INFO = true; let apiClient; @@ -22,8 +22,7 @@ return thisObject; - function initialize(callBackFunction) { - try { + function initialize() { exchangeId = bot.exchange.toLowerCase() let key = process.env.KEY @@ -41,18 +40,11 @@ verbose: false, options: options } - exchange = new exchangeClass(exchangeConstructorParams) - - callBackFunction(global.DEFAULT_OK_RESPONSE) - } catch (err) { - logError("initialize -> err = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } } function getMaxDecimalPositions() { - return 8 + return 8 } async function getTicker(market, callBackFunction) { @@ -62,13 +54,13 @@ let ticker if (exchange.has['fetchTicker']) { - ticker = await (exchange.fetchTicker(symbol)) + ticker = await (exchange.fetchTicker(symbol)) callBackFunction(global.DEFAULT_OK_RESPONSE, ticker) } else { logError("getTicker -> Exchange does not support fetchTicker command."); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } - + } catch (err) { logError("getTicker -> err = " + err.message); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -83,7 +75,7 @@ let openOrders if (exchange.has['fetchOpenOrders']) { - openOrders = await(exchange.fetchOpenOrders (symbol)) + openOrders = await (exchange.fetchOpenOrders(symbol)) callBackFunction(global.DEFAULT_OK_RESPONSE, openOrders) } else { logError("getOpenOrders -> Exchange does not support fetchOpenOrders command."); @@ -116,7 +108,7 @@ } } - async function createOrder(market, side, price, cost, amount, callBackFunction) { + async function createOrder(market, side, price, cost, amount, callBackFunction) { try { logInfo("createOrder -> Entering function."); @@ -137,7 +129,7 @@ let order if (exchange.has['createOrder']) { - order = await(exchange.createOrder(symbol, type, side, amount)) + order = await (exchange.createOrder(symbol, type, side, amount)) callBackFunction(global.DEFAULT_OK_RESPONSE, order) } else { logError("createOrder -> Exchange does not support createOrder command."); diff --git a/TaskServer/FileStorage.js b/TaskServer/FileStorage.js index 22966c706..1b55b9af7 100644 --- a/TaskServer/FileStorage.js +++ b/TaskServer/FileStorage.js @@ -22,11 +22,11 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { deleteTextFile: deleteTextFile } - return thisObject + return thisObject function getTextFile(filePath, callBackFunction, noRetry, canUsePrevious) { - let currentRetryGetTextFile = 0 + let currentRetryGetTextFile = 0 recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious) @@ -77,7 +77,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } callBackFunction(customResponse) return - } + } fileDoesNotExist = true logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> File does not exist. Retrying. ') @@ -106,7 +106,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { if (mustBeJason === false) { /* In this case there is nothing else to check. */ callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString()) - return + return } /* @@ -117,7 +117,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { /* The file was correctly read and could be parsed. */ callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString()) - return + return } catch (err) { @@ -140,11 +140,11 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> canUsePrevious -> err = ' + err.stack) callBackFunction(global.DEFAULT_FAIL_RESPONSE) return - } + } /* Returning the previous file */ callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString()) - return + return } } else { logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Could read the file, but could not parse it as it is not a valid JSON.') @@ -153,12 +153,12 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { setTimeout(retry, retryTimeToUse) return } - } + } } } catch (err) { - logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error writing file -> file = ' + fileLocation) - logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error writing file -> err = ' + err.stack) + logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error reading file -> file = ' + fileLocation) + logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error reading file -> err = ' + err.stack) retry() } @@ -191,16 +191,13 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } } - function createTextFile(filePath, fileContent, callBackFunction, keepPrevious) { + function createTextFile(filePath, fileContent, callBackFunction, keepPrevious, noTemp) { let currentRetryWriteTextFile = 0 - recursiveCreateTextFile(filePath, fileContent, callBackFunction, keepPrevious) - - function recursiveCreateTextFile(filePath, fileContent, callBackFunction, keepPrevious) { - - logger.write(MODULE_NAME, '[INFO] FileStorage -> createTextFile -> Entering Function.') + recursiveCreateTextFile(filePath, fileContent, callBackFunction, keepPrevious, noTemp) + function recursiveCreateTextFile(filePath, fileContent, callBackFunction, keepPrevious, noTemp) { /* Choose path for either logs or data */ let fileLocation if (filePath.indexOf("/Logs/") > 0) { @@ -210,7 +207,6 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } try { - logger.write(MODULE_NAME, '[INFO] FileStorage -> createTextFile -> fileLocation: ' + fileLocation) /* If necesary a folder or folders are created before writing the file to disk. */ @@ -222,7 +218,11 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { Then we delete the original file, if exists, and finally we rename the temporary into the original name. */ const fs = require('fs') - fs.writeFile(fileLocation + '.tmp', fileContent, onFileWritenn) + if (noTemp === true) { + fs.writeFile(fileLocation, fileContent, onFileWritenn) + } else { + fs.writeFile(fileLocation + '.tmp', fileContent, onFileWritenn) + } function onFileWritenn(err) { let retryTimeToUse = FAST_RETRY_TIME_IN_MILISECONDS @@ -235,6 +235,10 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { setTimeout(retry, retryTimeToUse) } else { + if (noTemp === true) { + callBackFunction(global.DEFAULT_OK_RESPONSE) + return + } if (keepPrevious === true) { /* In some cases, we are going to keep a copy of the previous version of the file being written. This will be usefull to recover from crashes @@ -252,7 +256,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> err = ' + err.stack) setTimeout(retry, retryTimeToUse) return - } + } /* Rename de Original into Previous */ fs.rename(fileLocation, fileLocation + '.Previous.json', onOriginalRenamed) @@ -266,7 +270,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> Error renaming original file -> file = ' + fileLocation) logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> Error renaming original file -> err = ' + err.stack) setTimeout(retry, retryTimeToUse) - return + return } /* Rename de Temp into Original */ @@ -282,13 +286,13 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { callBackFunction(global.DEFAULT_OK_RESPONSE) } - } + } } } } else { /* In this case, there is no need to keep a copy of the file being replaced, so we just delete and that's it. */ - fs.unlink(fileLocation , onUnlinked) + fs.unlink(fileLocation, onUnlinked) function onUnlinked(err) { let code = '' @@ -296,11 +300,11 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { code = err.code } if (code !== '' && code !== 'ENOENT') { - logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> file = ' + fileLocation ) + logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> file = ' + fileLocation) logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> err = ' + err.stack) setTimeout(retry, retryTimeToUse) return - } + } /* Rename de Temp into Original */ fs.rename(fileLocation + '.tmp', fileLocation, onTempRenamed) @@ -406,33 +410,33 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { /* Function to create folders of missing folders at any path. */ function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { - const sep = '/'; - const initDir = path.isAbsolute(targetDir) ? sep : ''; - const baseDir = isRelativeToScript ? __dirname : '.'; + const sep = '/'; + const initDir = path.isAbsolute(targetDir) ? sep : ''; + const baseDir = isRelativeToScript ? __dirname : '.'; - return targetDir.split(sep).reduce((parentDir, childDir) => { - const curDir = path.resolve(baseDir, parentDir, childDir); - try { - const fs = require('fs') - fs.mkdirSync(curDir); - } catch (err) { - if (err.code === 'EEXIST') { // curDir already exists! - return curDir; - } + return targetDir.split(sep).reduce((parentDir, childDir) => { + const curDir = path.resolve(baseDir, parentDir, childDir); + try { + const fs = require('fs') + fs.mkdirSync(curDir); + } catch (err) { + if (err.code === 'EEXIST') { // curDir already exists! + return curDir; + } - // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. - if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. - throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); - } + // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. + if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. + throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); + } - const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; - if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) { - throw err; // Throw if it's just the last created dir. - } - } + const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; + if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) { + throw err; // Throw if it's just the last created dir. + } + } - return curDir; - }, initDir); + return curDir; + }, initDir); } function getFileViaHTTP(filePath, callback, callBackFunction) { @@ -455,7 +459,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onError -> err = " + err.stack); logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onError -> Failed to fetch file via HTTP. Will retry later. "); callBackFunction(global.DEFAULT_RETRY_RESPONSE); - }) + }) function onResponse(response) { diff --git a/TaskServer/IndicatorBot.js b/TaskServer/IndicatorBot.js index 004ed077f..dbe4b844f 100644 --- a/TaskServer/IndicatorBot.js +++ b/TaskServer/IndicatorBot.js @@ -1,167 +1,805 @@ -exports.newIndicatorBot = function newIndicatorBot(bot, logger, UTILITIES, FILE_STORAGE) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; +exports.newIndicatorBot = function newIndicatorBot(bot, parentLogger) { const MODULE_NAME = "Indicator Bot"; + const FULL_LOG = true; + + let USER_BOT_MODULE; + let COMMONS_MODULE; + + const MULTI_PERIOD_MARKET = require(global.ROOT_DIR + 'MultiPeriodMarket'); + const MULTI_PERIOD_DAILY = require(global.ROOT_DIR + 'MultiPeriodDaily'); + const FILE_STORAGE = require('./FileStorage.js'); + let fileStorage = FILE_STORAGE.newFileStorage(parentLogger); + + const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog'); + let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble. + + let nextLoopTimeoutHandle; let thisObject = { initialize: initialize, - finalize: finalize, - start: start + run: run }; - let utilities = UTILITIES.newCloudUtilities(logger); - let fileStorage = FILE_STORAGE.newFileStorage(logger); - - const COMMONS = require('./Commons.js'); - let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE); + let processConfig; return thisObject; - function finalize() { - thisObject = undefined - utilities = undefined - fileStorage = undefined - commons = undefined - } - - function initialize(callBackFunction) { - + function initialize(pProcessConfig, callBackFunction) { try { + processConfig = pProcessConfig; - logger.fileName = MODULE_NAME; - logger.initialize(); + if (bot.definedByUI === true) { + /* The code of the bot is defined at the UI. No need to load a file with the code. */ + callBackFunction(global.DEFAULT_OK_RESPONSE); + return + } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } + /* This bot is not ready for taking its code from the UI, then we need to load it from its repo. */ + let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName + filePath += "/User.Bot.js" - callBackFunction(global.DEFAULT_OK_RESPONSE); + fileStorage.getTextFile(filePath, onBotDownloaded); + function onBotDownloaded(err, text) { + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message); + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + USER_BOT_MODULE = {} + USER_BOT_MODULE.newUserBot = eval(text); // TODO This needs to be changed function + + filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo; + filePath += "/Commons.js" + + fileStorage.getTextFile(filePath, onCommonsDownloaded); + + function onCommonsDownloaded(err, text) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.code || err.message); + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + COMMONS_MODULE = {} + COMMONS_MODULE.newCommons = eval(text); // TODO This needs to be changed function + + callBackFunction(global.DEFAULT_OK_RESPONSE); + } + } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function start(dataFiles, timeFrame, timeFrameLabel, currentDay, interExecutionMemory, callBackFunction) { - + function run(callBackFunction) { try { + let fixedTimeLoopIntervalHandle; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } + /* Errors sent to the UI */ + bot.processError = processError - let products = {} - let mainDependency = {} - let processingDailyFiles + /* Heartbeats sent to the UI */ + bot.processHeartBeat = processHeartBeat - if (currentDay !== undefined) { - processingDailyFiles = true + if (bot.runAtFixedInterval === true) { + fixedTimeLoopIntervalHandle = setInterval(loop, bot.fixedInterval); } else { - processingDailyFiles = false + loop(); } - /* The first phase here is about checking that we have everything we need at the definition level. */ - let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies - if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } + function loop() { + try { + function pad(str, max) { + str = str.toString(); + return str.length < max ? pad(" " + str, max) : str; + } - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets - if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + /* For each loop we want to create a new log file. */ + if (logger !== undefined) { + logger.finalize() + } + logger = DEBUG_MODULE.newDebugLog(); + global.LOGGER_MAP.set(MODULE_NAME, logger) + logger.bot = bot; + logger.initialize(); - /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + bot.loopCounter++; + bot.loopStartTime = new Date().valueOf(); - /* 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++) { + /* We tell the UI that we are running. */ + processHeartBeat() - 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. + /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ + const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities'); + const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport'); + const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies'); + const DATA_DEPENDENCIES = require(global.ROOT_DIR + 'DataDependencies'); + const DATA_SET = require(global.ROOT_DIR + 'DataSet'); + const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents'); + const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput'); - /* - For each outputDatasetNode in our process output, we will build the information based on our input products. - */ + /* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */ + bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution. - recordDefinition = outputDatasetNode.referenceParent.parentNode.record - singularVariableName = outputDatasetNode.referenceParent.parentNode.code.singularVariableName + /* High level log entry */ + console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30) + + " Main Loop # " + pad(Number(bot.loopCounter), 8)) - /* Check Irregular Periods */ + /* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */ + let processExecutionEvents + let userBot; + let processFramework; + let statusDependencies; + let dataDependencies; - if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.javascriptCode !== undefined) { - if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.javascriptCode.code.indexOf('results.push') >= 0) { - resultsWithIrregularPeriods = true + let nextWaitTime; + + initializeProcessExecutionEvents(); + + function initializeProcessExecutionEvents() { + try { + processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) + processExecutionEvents.initialize(onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well."); + startProcessExecutionEvents() + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function startProcessExecutionEvents() { + try { + processExecutionEvents.start(onStarted); + + function onStarted(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well."); + + if (global.STOP_TASK_GRACEFULLY === true) { + loopControl() + return + } + + initializeStatusDependencies(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function initializeStatusDependencies() { + try { + statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT); + statusDependencies.initialize(onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well."); + initializeDataDependencies(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function initializeDataDependencies() { + try { + dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET); + dataDependencies.initialize(onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + /* If the process is configured to run inside a framework, we continue there, otherwise we run the bot directly. */ + if (processConfig.framework === undefined) { + initializeUserBot(); + return; + } + + switch (processConfig.framework.name) { + case 'Multi-Period-Market': { + processFramework = MULTI_PERIOD_MARKET.newMultiPeriodMarket(bot, logger, UTILITIES, FILE_STORAGE); + intitializeProcessFramework(); + break; + } + case 'Multi-Period-Daily': { + processFramework = MULTI_PERIOD_DAILY.newMultiPeriodDaily(bot, logger, UTILITIES, FILE_STORAGE); + intitializeProcessFramework(); + break; + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported."); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + processConfig.framework.name); + global.unexpectedError = err.message + processStopped() + return + } + } + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + processStopped() + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function initializeUserBot() { + try { + usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE, UTILITIES, FILE_STORAGE); + usertBot.initialize(statusDependencies, onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> onInizialized -> Execution finished well."); + startUserBot(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeUserBot -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + case global.CUSTOM_OK_RESPONSE.result: { + + switch (err.message) { + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled custom response received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function startUserBot() { + try { + usertBot.start(onFinished); + + function onFinished(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> onFinished -> Execution finished well."); + finishProcessExecutionEvents() + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + case global.CUSTOM_OK_RESPONSE.result: { + + switch (err.message) { + case "Dependency does not exist.": { + logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Dependency does not exist. This Loop will go to sleep."); + nextWaitTime = 'Sleep'; + loopControl(nextWaitTime); + return; + } + case "Dependency not ready.": { + logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Dependency not ready. Will Retry Later."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled custom response received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function intitializeProcessFramework() { + try { + processFramework.initialize(processConfig, statusDependencies, dataDependencies, onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well."); + startProcessFramework(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + case global.CUSTOM_OK_RESPONSE.result: { + + switch (err.message) { + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function startProcessFramework() { + try { + processFramework.start(onFinished); + + function onFinished(err) { + try { + processFramework.finalize() + processFramework = undefined + dataDependencies.finalize() + dataDependencies = undefined + statusDependencies.finalize() + statusDependencies = undefined + + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well."); + finishProcessExecutionEvents() + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + case global.CUSTOM_OK_RESPONSE.result: { + + switch (err.message) { + case "Dependency does not exist.": { + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep."); + nextWaitTime = 'Sleep'; + loopControl(nextWaitTime); + return; + } + case "Dependency not ready.": { + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep."); + nextWaitTime = 'Sleep'; + loopControl(nextWaitTime); + return; + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function finishProcessExecutionEvents() { + try { + processExecutionEvents.finish(onFinished); + + function onFinished(err) { + try { + processExecutionEvents.finalize() + processExecutionEvents = undefined + + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well."); + nextWaitTime = 'Normal'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function loopControl(nextWaitTime) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); } + + /* We show we reached the end of the loop. */ + processHeartBeat() + + /* Here we check if we must stop the loop gracefully. */ + shallWeStop(onStop, onContinue); + + function onStop() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); } + processStopped() + return; + } + + function onContinue() { + /* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */ + switch (nextWaitTime) { + case 'Normal': { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.normalWaitTime / 1000) + " seconds."); } + nextLoopTimeoutHandle = setTimeout(loop, processConfig.normalWaitTime); + processHeartBeat(undefined, undefined, "Waiting " + processConfig.normalWaitTime / 1000 + " seconds for next execution.") + if (global.WRITE_LOGS_TO_FILES === 'true') { + logger.persist(); + } + } + break; + case 'Retry': { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); } + nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime); + processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.") + logger.persist(); + } + break; + case 'Sleep': { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); } + nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime); + processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.") + logger.persist(); + } + break; + case 'Coma': { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); } + nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime); + processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.") + logger.persist(); + } + break; + } + } + } + + function shallWeStop(stopCallBack, continueCallBack) { + try { + /* IMPORTANT: This function is exactly the same on the 3 modules. */ + if (!global.STOP_TASK_GRACEFULLY) { + continueCallBack(); + } else { + stopCallBack(); + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function processHeartBeat(processingDate, percentage, status) { + let event = { + seconds: (new Date()).getSeconds(), + processingDate: processingDate, + percentage: percentage, + status: status + } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) + } + + function processError(node, errorMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + errorMessage: errorMessage + } + } else { + event = { + errorMessage: errorMessage } } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Error', event) + } - /* Build the data */ - jsonData = commons.dataBuildingProcedure( - products, - mainDependency, - recordDefinition, - outputDatasetNode.referenceParent.parentNode.dataBuilding, - singularVariableName, - outputDatasetNode.referenceParent.parentNode.code.codeName, - timeFrame, - timeFrameLabel, - resultsWithIrregularPeriods, - interExecutionMemory, - processingDailyFiles, - currentDay - ) - - /* Add the calculated properties */ - if (outputDatasetNode.referenceParent.parentNode.calculations !== undefined) { - outputData = commons.calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timeFrame) + function processStopped() { + if (global.unexpectedError !== undefined) { + processError(undefined, "An unexpected error caused the Process to stop.") } else { - outputData = jsonData + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') } - 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.javascriptCode.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.dataMine = 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, currentDay, callBackFunction) - commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, timeFrameLabel, currentDay, callBackFunction) - } - - - function anotherFileWritten() { - totalFilesWritten++ - if (totalFilesWritten === outputDatasets.length) { - callBackFunction(global.DEFAULT_OK_RESPONSE); + logger.persist(); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); + if (global.unexpectedError !== undefined) { + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } else { + callBackFunction(global.DEFAULT_OK_RESPONSE) } } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); + + } catch (err) { + parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/TaskServer/IndicatorBotProcessMainLoop.js b/TaskServer/IndicatorBotProcessMainLoop.js deleted file mode 100644 index 90892dd72..000000000 --- a/TaskServer/IndicatorBotProcessMainLoop.js +++ /dev/null @@ -1,1121 +0,0 @@ -exports.newIndicatorBotProcessMainLoop = function newIndicatorBotProcessMainLoop(bot, parentLogger) { - - const ROOT_DIR = './'; - - const MODULE_NAME = "Indicator Process Main Loop"; - const FULL_LOG = true; - - let USER_BOT_MODULE; - let COMMONS_MODULE; - - const MULTI_PERIOD_MARKET = require(ROOT_DIR + 'MultiPeriodMarket'); - const MULTI_PERIOD_DAILY = require(ROOT_DIR + 'MultiPeriodDaily'); - const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage = FILE_STORAGE.newFileStorage(parentLogger); - - const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); - let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble. - - let nextLoopTimeoutHandle; - let checkLoopHealthHandle; - - let thisObject = { - initialize: initialize, - run: run - }; - - let processConfig; - - return thisObject; - - function initialize(pProcessConfig, callBackFunction) { - - try { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - processConfig = pProcessConfig; - - if (bot.definedByUI === true) { - /* The code of the bot is defined at the UI. No need to load a file with the code. */ - callBackFunction(global.DEFAULT_OK_RESPONSE); - return - } - - /* This bot is not ready for taking its code from the UI, then we need to load it from its repo. */ - - let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName - filePath += "/User.Bot.js" - - fileStorage.getTextFile(filePath, onBotDownloaded); - - function onBotDownloaded(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message); - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - - USER_BOT_MODULE = {} - USER_BOT_MODULE.newUserBot = eval(text); // TODO This needs to be changed function - - filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo; - filePath += "/Commons.js" - - fileStorage.getTextFile(filePath, onCommonsDownloaded); - - function onCommonsDownloaded(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.code || err.message); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - COMMONS_MODULE = {} - COMMONS_MODULE.newCommons = eval(text); // TODO This needs to be changed function - - callBackFunction(global.DEFAULT_OK_RESPONSE); - } - } - } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function run(callBackFunction) { - - try { - - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] run -> Entering function."); } - - bot.enableCheckLoopHealth = true; - - let fixedTimeLoopIntervalHandle; - - if (bot.runAtFixedInterval === true) { - - fixedTimeLoopIntervalHandle = setInterval(loop, bot.fixedInterval); - - } else { - - loop(); - - } - - function loop() { - - try { - - function pad(str, max) { - str = str.toString(); - return str.length < max ? pad(" " + str, max) : str; - } - - /* For each loop we want to create a new log file. */ - - if (logger !== undefined) { - logger.finalize() - } - logger = DEBUG_MODULE.newDebugLog(); - global.LOGGER_MAP.set(MODULE_NAME, logger) - logger.bot = bot; - logger.initialize(); - - bot.loopCounter++; - bot.loopStartTime = new Date().valueOf(); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Entering function."); } - - /* Loop Heartbeat sent to the UI */ - bot.processHeartBeat = processHeartBeat - processHeartBeat() - - /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ - - const UTILITIES = require(ROOT_DIR + 'CloudUtilities'); - const STATUS_REPORT = require(ROOT_DIR + 'StatusReport'); - const STATUS_DEPENDENCIES = require(ROOT_DIR + 'StatusDependencies'); - const DATA_DEPENDENCIES = require(ROOT_DIR + 'DataDependencies'); - const DATA_SET = require(ROOT_DIR + 'DataSet'); - const PROCESS_EXECUTION_EVENTS = require(ROOT_DIR + 'ProcessExecutionEvents'); - const PROCESS_OUTPUT = require(ROOT_DIR + 'ProcessOutput'); - - /* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */ - - bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution. - - /* High level log entry */ - - console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30) - + " Main Loop # " + pad(Number(bot.loopCounter), 8)) - - /* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */ - - let processExecutionEvents - let userBot; - let processFramework; - let statusDependencies; - let dataDependencies; - - let nextWaitTime; - - initializeProcessExecutionEvents(); - - function initializeProcessExecutionEvents() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> Entering function."); } - - processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) - - processExecutionEvents.initialize(onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well."); - startProcessExecutionEvents() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function startProcessExecutionEvents() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> Entering function."); } - - processExecutionEvents.start(onStarted); - - function onStarted(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well."); - - if (global.STOP_TASK_GRACEFULLY === true) { - loopControl() - return - } - - initializeStatusDependencies(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function initializeStatusDependencies() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> Entering function."); } - - statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT); - - statusDependencies.initialize(onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well."); - initializeDataDependencies(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function initializeDataDependencies() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> Entering function."); } - - dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET); - - dataDependencies.initialize(onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); } - - /* If the process is configured to run inside a framework, we continue there, otherwise we run the bot directly. */ - if (processConfig.framework === undefined) { - initializeUserBot(); - return; - } - - switch (processConfig.framework.name) { - case 'Multi-Period-Market': { - processFramework = MULTI_PERIOD_MARKET.newMultiPeriodMarket(bot, logger, UTILITIES, FILE_STORAGE); - intitializeProcessFramework(); - break; - } - case 'Multi-Period-Daily': { - processFramework = MULTI_PERIOD_DAILY.newMultiPeriodDaily(bot, logger, UTILITIES, FILE_STORAGE); - intitializeProcessFramework(); - break; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported."); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + processConfig.framework.name); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function initializeUserBot() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> Entering function."); } - - usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE, UTILITIES, FILE_STORAGE); - - usertBot.initialize(statusDependencies, onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> onInizialized -> Execution finished well."); - startUserBot(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeUserBot -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - case global.CUSTOM_OK_RESPONSE.result: { - - switch (err.message) { - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function startUserBot() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> Entering function."); } - - usertBot.start(onFinished); - - function onFinished(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> onFinished -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> onFinished -> Execution finished well."); - finishProcessExecutionEvents() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - case global.CUSTOM_OK_RESPONSE.result: { - - switch (err.message) { - case "Dependency does not exist.": { - logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Dependency does not exist. This Loop will go to sleep."); - nextWaitTime = 'Sleep'; - loopControl(nextWaitTime); - return; - } - case "Dependency not ready.": { - logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Dependency not ready. Will Retry Later."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function intitializeProcessFramework() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> Entering function."); } - - processFramework.initialize(processConfig, statusDependencies, dataDependencies, onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well."); - startProcessFramework(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - case global.CUSTOM_OK_RESPONSE.result: { - - switch (err.message) { - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function startProcessFramework() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> Entering function."); } - - processFramework.start(onFinished); - - function onFinished(err) { - - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Entering function."); } - processFramework.finalize() - processFramework = undefined - dataDependencies.finalize() - dataDependencies = undefined - statusDependencies.finalize() - statusDependencies = undefined - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well."); - finishProcessExecutionEvents() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - case global.CUSTOM_OK_RESPONSE.result: { - - switch (err.message) { - case "Dependency does not exist.": { - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep."); - nextWaitTime = 'Sleep'; - loopControl(nextWaitTime); - return; - } - case "Dependency not ready.": { - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep."); - nextWaitTime = 'Sleep'; - loopControl(nextWaitTime); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function finishProcessExecutionEvents() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> Entering function."); } - - processExecutionEvents.finish(onFinished); - - function onFinished(err) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Entering function."); } - - processExecutionEvents.finalize() - processExecutionEvents = undefined - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well."); - nextWaitTime = 'Normal'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function loopControl(nextWaitTime) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Entering function."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); } - - /* We show we reached the end of the loop. */ - - processHeartBeat() - - /* Here we check if we must stop the loop gracefully. */ - - shallWeStop(onStop, onContinue); - - function onStop() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Entering function."); } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); } - - if(global.WRITE_LOGS_TO_FILES === 'true'){ - logger.persist(); - } - - processStopped() - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - - } - - function onContinue() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onContinue -> Entering function."); } - - /* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */ - - switch (nextWaitTime) { - case 'Normal': { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.normalWaitTime / 1000) + " seconds."); } - if (processConfig.deadWaitTime > 0) { - checkLoopHealthHandle = setTimeout(checkLoopHealth, processConfig.deadWaitTime, bot.loopCounter); - } - nextLoopTimeoutHandle = setTimeout(loop, processConfig.normalWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.normalWaitTime / 1000 + " seconds for next execution.") - if(global.WRITE_LOGS_TO_FILES === 'true'){ - logger.persist(); - } - } - break; - case 'Retry': { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); } - if (processConfig.deadWaitTime > 0) { - checkLoopHealthHandle = setTimeout(checkLoopHealth, processConfig.deadWaitTime, bot.loopCounter); - } - nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime); - processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.") - logger.persist(); - } - break; - case 'Sleep': { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); } - nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.") - logger.persist(); - } - break; - case 'Coma': { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); } - nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.") - logger.persist(); - } - break; - } - } - } - - function checkLoopHealth(pLastLoop) { - - if (bot.enableCheckLoopHealth === false || global.STOP_TASK_GRACEFULLY === true) { - - logger.write(MODULE_NAME, "[WARN] run -> loop -> checkLoopHealth -> bot.enableCheckLoopHealth = " + bot.enableCheckLoopHealth); - - return; - } // This gets disabled anytime the Main Loop is shut down by any condition. - - if (bot.loopCounter <= pLastLoop + 1) { // This means that the next loop started but also stopped executing abruptally. - - let now = new Date().valueOf(); - - if (now - bot.loopStartTime > processConfig.deadWaitTime) { - - logger.write(MODULE_NAME, "[ERROR] run -> loop -> checkLoopHealth -> Dead loop found -> pLastLoop = " + pLastLoop); - console.log((new Date().toISOString() + " [ERROR] run -> loop -> checkLoopHealth -> " + pad(bot.codeName,20) + " " + pad(bot.process,30) + " Loop # " + pad(Number(bot.loopCounter),5) + " found dead. Resurrecting it now.")); - - logger.persist(); // We persist the logs of the failed execution. - clearTimeout(nextLoopTimeoutHandle); // We cancel the timeout in case the original loop was still running and schedulled to reexecute. - loop(); // We restart the loop so that the processing can continue. - - } else { - - console.log((new Date().toISOString() + " [WARN] run -> loop -> checkLoopHealth -> " + pad(bot.codeName,20) + " " + pad(bot.process,30) + " Loop # " + pad(Number(bot.loopCounter),5) + " found delayed but still alive. No action taken.")); - - } - } - } - - function shallWeStop(stopCallBack, continueCallBack) { - - try { - - /* IMPORTANT: This function is exactly the same on the 3 modules. */ - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> shallWeStop -> Entering function. "); } - - if (!global.STOP_TASK_GRACEFULLY) { - continueCallBack(); - } else { - stopCallBack(); - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = "+ err.stack); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function processHeartBeat(processingDate, percentage, status) { - let event = { - seconds: (new Date()).getSeconds(), - processingDate: processingDate, - percentage: percentage, - status: status - } - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) - } - - function processStopped() { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') - } - - } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] run -> err = "+ err.stack); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } -}; diff --git a/TaskServer/IndicatorOutput.js b/TaskServer/IndicatorOutput.js new file mode 100644 index 000000000..3faa4052e --- /dev/null +++ b/TaskServer/IndicatorOutput.js @@ -0,0 +1,157 @@ +exports.newIndicatorOutput = function newIndicatorOutput(bot, logger, UTILITIES, FILE_STORAGE) { + const FULL_LOG = true; + const MODULE_NAME = "Indicator Bot"; + + let thisObject = { + initialize: initialize, + finalize: finalize, + start: start + }; + + let utilities = UTILITIES.newCloudUtilities(logger); + let fileStorage = FILE_STORAGE.newFileStorage(logger); + + const COMMONS = require('./Commons.js'); + let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE); + + return thisObject; + + function finalize() { + thisObject = undefined + utilities = undefined + fileStorage = undefined + commons = undefined + } + + function initialize(callBackFunction) { + + try { + + logger.fileName = MODULE_NAME; + logger.initialize(); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } + + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function start(dataFiles, timeFrame, timeFrameLabel, currentDay, interExecutionMemory, callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } + + let products = {} + let mainDependency = {} + let processingDailyFiles + + if (currentDay !== undefined) { + processingDailyFiles = true + } else { + processingDailyFiles = false + } + + /* The first phase here is about checking that we have everything we need at the definition level. */ + let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } + + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + + /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + + /* 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.config.singularVariableName + + /* Check Irregular Periods */ + + if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.javascriptCode !== undefined) { + if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.javascriptCode.code.indexOf('results.push') >= 0) { + resultsWithIrregularPeriods = true + } + } + + /* Build the data */ + jsonData = commons.dataBuildingProcedure( + products, + mainDependency, + recordDefinition, + outputDatasetNode.referenceParent.parentNode.dataBuilding, + singularVariableName, + outputDatasetNode.referenceParent.parentNode.config.codeName, + timeFrame, + timeFrameLabel, + resultsWithIrregularPeriods, + interExecutionMemory, + processingDailyFiles, + currentDay + ) + + /* Add the calculated properties */ + if (outputDatasetNode.referenceParent.parentNode.calculations !== undefined) { + outputData = commons.calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timeFrame) + } else { + outputData = jsonData + } + products[outputDatasetNode.referenceParent.parentNode.config.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.config.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.javascriptCode.code.indexOf('results.push') >= 0) { + resultsWithIrregularPeriods = true + } + + /* Simplifying the access to basic info */ + + contextSummary.dataset = outputDatasetNode.referenceParent.config.codeName + contextSummary.product = outputDatasetNode.referenceParent.parentNode.config.codeName + contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName + contextSummary.dataMine = outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.config.codeName + + let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, currentDay, callBackFunction) + commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, timeFrameLabel, currentDay, callBackFunction) + } + + + function anotherFileWritten() { + totalFilesWritten++ + if (totalFilesWritten === outputDatasets.length) { + callBackFunction(global.DEFAULT_OK_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } +}; diff --git a/TaskServer/LowFrequencyTrading/Announcements.js b/TaskServer/LowFrequencyTrading/Announcements.js new file mode 100644 index 000000000..ec8c25d75 --- /dev/null +++ b/TaskServer/LowFrequencyTrading/Announcements.js @@ -0,0 +1,61 @@ +exports.newAnnouncements = function newAnnouncements(bot, logger) { + + const MODULE_NAME = 'Announcements' + + let thisObject = { + makeAnnoucements: makeAnnoucements, + initialize: initialize, + finalize: finalize + } + + let tradingSystem + let tradingEngine + + return thisObject + + function initialize() { + tradingSystem = bot.simulationState.tradingSystem + tradingEngine = bot.simulationState.tradingEngine + } + + function finalize() { + tradingSystem = undefined + tradingEngine = undefined + bot.SESSION.messagesSent = undefined + } + + function makeAnnoucements(node) { + if (node.announcements !== undefined) { + for (let i = 0; i < node.announcements.length; i++) { + let announcement = node.announcements[i] + let canAnnounce = true + if (announcement.announcementCondition !== undefined) { + let conditionValue + try { + conditionValue = eval(announcement.announcementCondition.code) + } catch (err) { + tradingSystem.errors.push([announcement.announcementCondition.id, err.message]) + } + if (conditionValue !== true) { canAnnounce = false } + } + if (canAnnounce === true) { + let text = announcement.config.text + if (announcement.formula !== undefined) { + try { + text = eval(announcement.formula.code) + } catch (err) { + tradingSystem.errors.push([announcement.formula.id, err.message]) + } + } + + if (bot.SESSION.socialBots !== undefined) { + bot.SESSION.socialBots.announce(text) + tradingSystem.announcements.push([announcement.id, text]) + } else { + tradingSystem.errors.push([announcement.id, 'Could not announce because session does not have Social Bots.']) + } + } + } + } + } +} \ No newline at end of file diff --git a/TaskServer/LowFrequencyTrading/Snapshots.js b/TaskServer/LowFrequencyTrading/Snapshots.js new file mode 100644 index 000000000..d36073854 --- /dev/null +++ b/TaskServer/LowFrequencyTrading/Snapshots.js @@ -0,0 +1,336 @@ +exports.newSnapshots = function newSnapshots(bot, logger) { + /* + This module encapsulates the snapshots functionality. + */ + const MODULE_NAME = 'Snapshots' + + let thisObject = { + reset: reset, + strategyEntry: strategyEntry, + strategyExit: strategyExit, + positionEntry: positionEntry, + positionExit: positionExit, + addCodeToSnapshot: addCodeToSnapshot, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + let tradingSystem + let sessionParameters + + const SNAPSHOTS_FOLDER_NAME = 'Snapshots' + + /* Snapshots of Strategies and Positions */ + let snapshots = { + headers: [], + strategies: [], + positions: [] + } + + let positionKeys = new Map() // This is to prevent adding the same indicator / variable to the output collection. + let strategyKeys = new Map() + let createHeaders = true // This is going to be true only once + let createCloseHeaders = true // This is going to be true only once + let closeValues + let strategyValues + let addToStrategyValues + let positionValues + let addToPositionValues + + let chart + + return thisObject + + function initialize() { + tradingEngine = bot.simulationState.tradingEngine + tradingSystem = bot.simulationState.tradingSystem + sessionParameters = bot.SESSION.parameters + } + + function finalize() { + writeSnapshotFiles() + tradingEngine = undefined + tradingSystem = undefined + sessionParameters = undefined + + positionKeys = undefined + strategyKeys = undefined + + chart = undefined + } + + function reset(pChart) { + /* This function helps reset data structures at every cycle of the simulation loop/=. */ + chart = pChart // We need chat to be a local object accessible from conditions and formulas. + } + + function strategyEntry() { + /* + We will record here a snapshot of all values used in conditions and formulas within the Trading System. + If the strategy exists with a trade, this information will be added to the snapshots file. + */ + + strategyKeys = new Map() + strategyValues = [] + addToStrategyValues = true + + evalNode(tradingSystem) + + /* Turn off this */ + createHeaders = false + addToStrategyValues = false + } + + function strategyExit() { + getResults(tradingEngine.current.strategy.begin.value, tradingEngine.current.strategy.end.value) + let valuesArray = closeValues.concat(strategyValues) + snapshots.strategies.push(valuesArray) + } + + function positionEntry() { + + positionKeys = new Map() + positionValues = [] + addToPositionValues = true + + evalNode(tradingSystem) + + /* Turn off this */ + createHeaders = false + addToPositionValues = false + } + + function positionExit() { + getResults(tradingEngine.current.position.begin.value, tradingEngine.current.position.end.value) + let valuesArray = closeValues.concat(positionValues) + snapshots.positions.push(valuesArray) + } + + function evalNode(node) { + if (node === undefined) { return } + + /* Here we check if there is a codition to be evaluated */ + if (node.type === 'Condition') { + /* We will eval this condition */ + evalCondition(node) + } + + /* Here we check if there is a formula to be evaluated */ + if (node.type === 'Formula') { + if (node.code !== undefined) { + /* We will eval this formula */ + evalFormula(node) + } + } + + /* Now we go down through all this node children */ + let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + evalNode(node[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + for (let m = 0; m < nodePropertyArray.length; m++) { + evalNode(nodePropertyArray[m]) + } + } + break + } + } + } + } + } + + function evalCondition(node) { + + /* + The code can be at the condition node if it was done with the Conditions Editor, or it can also be + at a Javascript Code node. If there is a Javascript object we will give it preference and take the code + from there. Otherwise we will take the code from the Condition node. + */ + + let nodeWithCode = node + if (node.javascriptCode !== undefined) { + if (node.javascriptCode.code !== undefined) { + nodeWithCode = node.javascriptCode + } + } + addCodeToSnapshot(nodeWithCode) + } + + function evalFormula(node) { + let nodeWithCode = node + addCodeToSnapshot(nodeWithCode) + } + + function addCodeToSnapshot(nodeWithCode) { + if (nodeWithCode === undefined) { return } + if (nodeWithCode.code === undefined) { return } + + try { + let instructionsArray = nodeWithCode.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, '') + 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 + + if (addToStrategyValues === true) { + addValues(strategyKeys, strategyValues) + } + + if (addToPositionValues === true) { + addValues(positionKeys, positionValues) + } + + function addValues(keys, values) { + existingKey = keys.get(key) + + if (existingKey === undefined) { // means that at the current loop this property of this product was not used before. + keys.set(key, key) + if (createHeaders === true) { + snapshots.headers.push(key) + } + + let value = eval(instruction) + values.push(value) + } + } + } + } + } catch (err) { + tradingSystem.errors.push(nodeWithCode.id, err.message) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> nodeWithCode.code = ' + nodeWithCode.code) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack) + } + } + + function getResults(openDatetime, closeDatetime) { + + let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type'] + closeValues = [ + tradingEngine.episode.episodeCounters.positions.value, // Position Number + (new Date(openDatetime)).toISOString(), // Open Datetime + (new Date(closeDatetime)).toISOString(), // Open Datetime + tradingEngine.current.strategy.strategyName.value, // Strategy Name + tradingEngine.current.strategy.situationName.value, // Trigger On Situation + tradingEngine.current.position.situationName.value, // Take Position Situation + tradingEngine.current.position.positionStatistics.hitFail.value, // Result + tradingEngine.current.position.positionStatistics.ROI.value, // ROI + tradingEngine.current.position.exitType.value // Exit Type + ] + + if (createCloseHeaders === true) { + snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshots.headers))) + createCloseHeaders = false + } + } + + function writeSnapshotFiles() { + + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.strategy === true) { + writeSnapshotFile(snapshots.strategies, 'Strategies') + } + } + + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.strategy === true) { + writeSnapshotFile(snapshots.positions, 'Positions') + } + } + } + + function writeSnapshotFile(snapshotArray, pFileName) { + try { + const FILE_STORAGE = require('../FileStorage.js'); + let fileStorage = FILE_STORAGE.newFileStorage(logger); + + let fileContent = ""; + let separator = "\r\n"; + + parseRecord(snapshots.headers) + + for (let i = 0; i < snapshotArray.length; i++) { + let record = snapshotArray[i]; + parseRecord(record) + } + + 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 + ""; + + let fileName = pFileName + '.csv'; + let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SNAPSHOTS_FOLDER_NAME; + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> err = " + err.stack); + } + } +} \ No newline at end of file diff --git a/TaskServer/LowFrequencyTrading/TradingEngine.js b/TaskServer/LowFrequencyTrading/TradingEngine.js new file mode 100644 index 000000000..7a549481d --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingEngine.js @@ -0,0 +1,168 @@ +exports.newTradingEngine = function newTradingEngine(bot, logger) { + /* + We call the Trading Engine to the data structure that is needed in order to exevute the + trading protocol with the specific rules defined at the Trading System. + */ + const MODULE_NAME = 'Trading Engine' + let thisObject = { + cloneValues: cloneValues, + setCurrentCandle: setCurrentCandle, + updateEpisodeCountersAndStatistics: updateEpisodeCountersAndStatistics, + updateDistanceToEventsCounters: updateDistanceToEventsCounters, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + let sessionParameters + + return thisObject + + function initialize() { + /* Here we will go through all the nodes in the Trading Engine hiriarchy and apply the initial value to the value property when needed */ + + tradingEngine = bot.simulationState.tradingEngine + sessionParameters = bot.SESSION.parameters + + if (bot.FIRST_EXECUTION === true) { + initializeNode(tradingEngine) + tradingEngine.current.balance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.balance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance + } + } + + function finalize() { + tradingEngine = undefined + sessionParameters = undefined + } + + function setCurrentCandle(candle) { + cloneValues(tradingEngine.current.candle, tradingEngine.previous.candle) + + tradingEngine.current.candle.begin.value = candle.begin + tradingEngine.current.candle.end.value = candle.end + tradingEngine.current.candle.open.value = candle.open + tradingEngine.current.candle.close.value = candle.close + tradingEngine.current.candle.min.value = candle.min + tradingEngine.current.candle.max.value = candle.max + } + + function cloneValues(originNode, destinationNode) { + if (originNode === undefined) { return } + if (destinationNode === undefined) { return } + + /* Here copy the node value */ + destinationNode.value = originNode.value + + /* Now we go down through all the children of the origin node, assuming the destination node has the same children structure*/ + let nodeDefinition = bot.APP_SCHEMA_MAP.get(originNode.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) { + cloneValues(originNode[property.name], destinationNode[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) { + let originNodePropertyArray = originNode[property.name] + let destinationNodePropertyArray = destinationNode[property.name] + for (let m = 0; m < originNodePropertyArray.length; m++) { + cloneValues(originNodePropertyArray[m], destinationNodePropertyArray[m]) + } + } + break + } + } + } + } + } + + function initializeNode(node) { + if (node === undefined) { return } + + /* Here we initialize the node value */ + if (node.config !== undefined) { + if (node.config.initialValue !== undefined) { + node.value = node.config.initialValue + } + } + node.initialize = initializeNode // This will allow anyone to initialize this node and its children. + + /* Now we go down through all this node children */ + let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + initializeNode(node[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + for (let m = 0; m < nodePropertyArray.length; m++) { + initializeNode(nodePropertyArray[m]) + } + } + break + } + } + } + } + } + + function updateEpisodeCountersAndStatistics() { + tradingEngine.episode.episodeCounters.periods.value++ + tradingEngine.episode.episodeStatistics.days.value = tradingEngine.episode.episodeCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + } + + function updateDistanceToEventsCounters() { + /* Keeping Distance Counters Up-to-date */ + if ( + tradingEngine.current.distanceToEvent.triggerOn.value > 0 // with this we avoind counting before the first event happens. + ) { + tradingEngine.current.distanceToEvent.triggerOn.value++ + } + + if ( + tradingEngine.current.distanceToEvent.triggerOff.value > 0 // with this we avoind counting before the first event happens. + ) { + tradingEngine.current.distanceToEvent.triggerOff.value++ + } + + if ( + tradingEngine.current.distanceToEvent.takePosition.value > 0 // with this we avoind counting before the first event happens. + ) { + tradingEngine.current.distanceToEvent.takePosition.value++ + } + + if ( + tradingEngine.current.distanceToEvent.closePosition.value > 0 // with this we avoind counting before the first event happens. + ) { + tradingEngine.current.distanceToEvent.closePosition.value++ + } + } + +} + diff --git a/TaskServer/LowFrequencyTrading/TradingExecution.js b/TaskServer/LowFrequencyTrading/TradingExecution.js new file mode 100644 index 000000000..edf84a9d0 --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingExecution.js @@ -0,0 +1,319 @@ +exports.newTradingExecution = function newTradingExecution(bot, logger) { + /* + The Trading Execution modules manages the execution of orders against the exchanges. + */ + const MODULE_NAME = 'Trading Execution' + + let thisObject = { + takePosition: takePosition, + closePosition: closePosition, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + + return thisObject + + function initialize() { + + tradingEngine = bot.simulationState.tradingEngine + + } + + function finalize() { + tradingEngine = undefined + } + + function takePosition() { + /* Check that we are in LIVE MODE */ + if (bot.SESSION.type === 'Live Trading Session' || bot.SESSION.type === 'Fordward Testion Session') { + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> Taking a Position in ' + bot.SESSION.type) + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.size.value = ' + tradingEngine.current.position.size.value) + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.rate.value = ' + tradingEngine.current.position.rate.value) + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> slippageAmount = ' + slippageAmount) + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.rate.value = ' + tradingEngine.current.position.rate.value) + + /* We see if we need to put the actual order at the exchange. */ + if (variable.executionContext !== undefined) { + switch (variable.executionContext.status) { + case 'Without a Position': { // We need to put the order because It was not put yet. + if (strategy.openStage !== undefined) { + if (strategy.openStage.openExecution !== undefined) { + putOpeningOrder() + return + } + } + break + } + case 'Position Closed': { // Waiting for a confirmation that the position was closed. + if (strategy.openStage !== undefined) { + if (strategy.openStage.openExecution !== undefined) { + putOpeningOrder() + return + } + } + break + } + case 'Taking Position': { // Waiting for a confirmation that the position was taken. + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is Taking Position.') + break + } + case 'In a Position': { // This should mean that we already put the order at the exchange. + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is In a Position.') + break + } + } + } else { // The context does not exist so it means we are not in a position. + if (strategy.openStage !== undefined) { + if (strategy.openStage.openExecution !== undefined) { + putOpeningOrder() + return + } + } + } + } else { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not trading live.') + } + + } + + function putOpeningOrder() { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function.') + + /* We wont take a position unless we are withing the sessionParameters.timeRange.config.initialDatetime and the sessionParameters.timeRange.config.finalDatetime range */ + if (sessionParameters.timeRange.config.initialDatetime !== undefined) { + if (candle.end < sessionParameters.timeRange.config.initialDatetime) { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because current candle ends before the start date. -> sessionParameters.timeRange.config.initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) + takePositionAtSimulation() + return + } + } + + /* We wont take a position if we are past the final datetime */ + if (sessionParameters.timeRange.config.finalDatetime !== undefined) { + if (candle.begin > sessionParameters.timeRange.config.finalDatetime) { + logger.write(MODULE_NAME, '[INFO] runSimulation -> putOpeningOrder -> Not placing the trade at the exchange because current candle begins after the end date. -> sessionParameters.timeRange.config.finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) + takePositionAtSimulation() + return + } + } + + /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ + if (variable.executionContext !== undefined) { + if (variable.executionContext.periods !== undefined) { + if (tradingEngine.episode.episodeCounters.periods.value <= variable.executionContext.periods) { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because it was already placed at a previous execution.') + takePositionAtSimulation() + return + } + } + } + + /* We are not going to place orders based on outdated information. The next filter prevents firing orders when backtesting. */ + if (currentDay) { + let today = new Date(Math.trunc((new Date().valueOf()) / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) + let processDay = new Date(Math.trunc(currentDay.valueOf() / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) + if (today.valueOf() !== processDay.valueOf()) { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because the current candle belongs to the previous day and that is considered simulation and not live trading.') + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> today = ' + today) + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = ' + processDay) + takePositionAtSimulation() + return + } + } + + let orderPrice + let amountA + let amountB + let orderSide + + if (bot.sessionAndMarketBaseAssetsAreEqual) { + orderSide = 'sell' + + orderPrice = tradingEngine.current.position.rate.value + + amountA = tradingEngine.current.position.size.value * orderPrice + amountB = tradingEngine.current.position.size.value + } else { + orderSide = 'buy' + + orderPrice = tradingEngine.current.position.rate.value + + amountA = tradingEngine.current.position.size.value + amountB = tradingEngine.current.position.size.value / orderPrice + } + + variable.executionContext = { + status: 'Taking Position', + periods: tradingEngine.episode.episodeCounters.periods.value + } + + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Ready to create order.') + exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) + + function onOrderCreated(err, order) { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function.') + + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') + variable.executionContext = { + status: 'In a Position', + periods: tradingEngine.episode.episodeCounters.periods.value, + amountA: amountA, + amountB: amountB, + orderId: order.id + } + takePositionAtSimulation() + return + } + case global.DEFAULT_FAIL_RESPONSE.result: { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) + strategy.openStage.openExecution.error = err.message + afterLoop() + return + } + case global.DEFAULT_RETRY_RESPONSE.result: { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) + strategy.openStage.openExecution.error = err.message + afterLoop() + return + } + } + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } + } + } + + function closePosition() { + /* Check that we are in LIVE MODE */ + if (bot.SESSION.type === 'Live Trading Session' || bot.SESSION.type === 'Fordward Testion Session') { + /* We see if we need to put the actual order at the exchange. */ + if (variable.executionContext !== undefined) { + switch (variable.executionContext.status) { + case 'Without a Position': { // No way to close anything at the exchange. + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Without a Position.') + break + } + case 'In a Position': { // This should mean that we already put the order at the exchange. + if (strategy.closeStage !== undefined) { + if (strategy.closeStage.closeExecution !== undefined) { + putClosingOrder() + return + } + } + break + } + case 'Closing Position': { // Waiting for a confirmation that the position was taken. + if (strategy.closeStage !== undefined) { + if (strategy.closeStage.closeExecution !== undefined) { + putClosingOrder() + return + } + } + break + } + + case 'Position Closed': { // + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed.') + break + } + } + } + } + } + + function putClosingOrder() { + /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ + if (variable.executionContext !== undefined) { + if (variable.executionContext.periods !== undefined) { + if (tradingEngine.episode.episodeCounters.periods.value <= variable.executionContext.periods) { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> Exiting function because this closing was already submited at a previous execution.') + closePositionAtSimulation() + return + } + } + } + + let orderPrice + let amountA + let amountB + let orderSide + + if (bot.sessionAndMarketBaseAssetsAreEqual) { + orderSide = 'buy' + + orderPrice = ticker.last + 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. + + amountA = tradingEngine.current.balance.quotedAsset.value + amountB = tradingEngine.current.balance.quotedAsset.value / orderPrice + } else { + orderSide = 'sell' + + orderPrice = ticker.last - 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. + + amountA = tradingEngine.current.balance.baseAsset.value * orderPrice + amountB = tradingEngine.current.balance.baseAsset.value + } + + variable.executionContext = { + status: 'Closing Position', + periods: tradingEngine.episode.episodeCounters.periods.value + } + + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> About to close position at the exchange.') + exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) + + function onOrderCreated(err, order) { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function.') + + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') + variable.executionContext = { + status: 'Position Closed', + periods: tradingEngine.episode.episodeCounters.periods.value, + amountA: amountA, + amountB: amountB, + orderId: order.id + } + closePositionAtSimulation() + return + } + case global.DEFAULT_FAIL_RESPONSE.result: { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') + /* We will assume that the problem is temporary, and expect that it will work at the next execution. */ + strategy.closeStage.closeExecution.error = err.message + afterLoop() + return + } + case global.DEFAULT_RETRY_RESPONSE.result: { + logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') + strategy.closeStage.closeExecution.error = err.message + afterLoop() + return + } + } + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } + } + } +} + diff --git a/TaskServer/LowFrequencyTrading/TradingOutput.js b/TaskServer/LowFrequencyTrading/TradingOutput.js new file mode 100644 index 000000000..0abe8c40a --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingOutput.js @@ -0,0 +1,276 @@ +exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE) { + /* + This module will load if necesary all the data outputs so that they can be appended with new + records if needed. After running the simulation, it will save all the data outputs. + */ + const FULL_LOG = true + const MODULE_NAME = 'Trading Bot' + + let thisObject = { + initialize: initialize, + finalize: finalize, + start: start + } + + let utilities = UTILITIES.newCloudUtilities(logger) + let fileStorage = FILE_STORAGE.newFileStorage(logger) + + return thisObject + + function finalize() { + thisObject = undefined + utilities = undefined + fileStorage = undefined + commons = undefined + } + + function initialize(callBackFunction) { + try { + logger.fileName = MODULE_NAME + logger.initialize() + + callBackFunction(global.DEFAULT_OK_RESPONSE) + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] initialize -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function start(chart, timeFrame, timeFrameLabel, currentDay, callBackFunction) { + try { + bot.processingDailyFiles + if (timeFrame > global.dailyFilePeriods[0][0]) { + bot.processingDailyFiles = false + } else { + bot.processingDailyFiles = true + } + + /* Preparing everything for the Simulation */ + const TRADING_SIMULATION = require('./TradingSimulation.js') + let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES) + + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + let market = bot.market + let totalFilesToBeRead = 0 + let totalFilesRead = 0 + let totalFilesToBeWritten = 0 + let totalFilesWritten = 0 + let outputDatasetsMap = new Map() + + if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { + /* + Here is where the Trading Engine and Trading Systems received are moved to the simulation state. + */ + bot.simulationState.tradingEngine = bot.TRADING_ENGINE + bot.simulationState.tradingSystem = bot.TRADING_SYSTEM + initializeOutputs() + } else { + readFiles() + } + return + + function initializeOutputs() { + if (bot.processingDailyFiles) { + initializeDailyFiles() + } else { + initializeMarketFiles() + } + } + + function initializeDailyFiles() { + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let dataset = outputDatasetNode.referenceParent + + if (dataset.config.type === 'Daily Files') { + outputDatasetsMap.set(dataset.parentNode.config.codeName, []) + } + } + runSimulation() + } + + function initializeMarketFiles() { + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let dataset = outputDatasetNode.referenceParent + + if (dataset.config.type === 'Market Files') { + outputDatasetsMap.set(dataset.parentNode.config.codeName, []) + } + } + runSimulation() + } + + function readFiles() { + /* + This bot have an output of files that it generates. At every call to the bot, it needs to read the previously generated + files in order to later append more information after the execution is over. Here in this function we are going to + read those output files and get them ready for appending content during the simulation. + */ + if (bot.processingDailyFiles) { + readDailyFiles() + } else { + readMarketFiles() + } + } + + function readMarketFiles() { + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let dataset = outputDatasetNode.referenceParent + + if (dataset.config.type === 'Market Files') { + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + + readOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + } + } + } + + function readDailyFiles() { + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let dataset = outputDatasetNode.referenceParent + + if (dataset.config.type === 'Daily Files') { + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath + + readOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + } + } + } + + function readOutputFile(fileName, filePath, productName) { + totalFilesToBeRead++ + filePath += '/' + fileName + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + + if (err.message === 'File does not exist.') { + outputDatasetsMap.set(productName, []) + anotherFileRead() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + outputDatasetsMap.set(productName, JSON.parse(text)) + anotherFileRead() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function anotherFileRead() { + totalFilesRead++ + if (totalFilesRead === totalFilesToBeRead) { + runSimulation() + } + } + + function runSimulation() { + tradingSimulation.runSimulation( + chart, + outputDatasetsMap, + writeFiles, + callBackFunction) + } + + function writeFiles() { + /* + The output of files which were appended with information during the simulation execution, now needs to be saved. + */ + if (bot.processingDailyFiles) { + writeDailyFiles() + } else { + writeMarketFiles() + } + } + + function writeMarketFiles() { + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let dataset = outputDatasetNode.referenceParent + + if (dataset.config.type === 'Market Files') { + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + + writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + } + } + } + + function writeDailyFiles() { + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let dataset = outputDatasetNode.referenceParent + + if (dataset.config.type === 'Daily Files') { + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath + + writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + } + } + } + + function writeOutputFile(fileName, filePath, productName) { + totalFilesToBeWritten++ + filePath += '/' + fileName + let fileContent = JSON.stringify(outputDatasetsMap.get(productName)) + + fileStorage.createTextFile(filePath, fileContent, onFileCreated) + + function onFileCreated(err) { + try { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + anotherFileWritten() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function anotherFileWritten() { + totalFilesWritten++ + if (totalFilesWritten === totalFilesToBeWritten) { + callBackFunction(global.DEFAULT_OK_RESPONSE) + } + } + + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } +} + diff --git a/TaskServer/LowFrequencyTrading/TradingPosition.js b/TaskServer/LowFrequencyTrading/TradingPosition.js new file mode 100644 index 000000000..ad7bc42fc --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingPosition.js @@ -0,0 +1,204 @@ +exports.newTradingPosition = function newTradingPosition(bot, logger) { + /* + This module packages all functions related to Positions. + */ + const MODULE_NAME = 'Trading Position' + let thisObject = { + openPosition: openPosition, + closePosition: closePosition, + applyStopLossFormula: applyStopLossFormula, + applyTakeProfitFormula: applyTakeProfitFormula, + preventStopLossDistortion: preventStopLossDistortion, + applySlippageToStopLoss: applySlippageToStopLoss, + preventTakeProfitDistortion: preventTakeProfitDistortion, + applySlippageToTakeProfit: applySlippageToTakeProfit, + updateStopLoss: updateStopLoss, + updateTakeProfit: updateTakeProfit, + updateSizeAndRate: updateSizeAndRate, + applySlippageToRate: applySlippageToRate, + updateEnds: updateEnds, + updateStatus: updateStatus, + updateCounters: updateCounters, + updateStatistics: updateStatistics, + resetPosition: resetPosition, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + let sessionParameters + + return thisObject + + function initialize() { + tradingEngine = bot.simulationState.tradingEngine + sessionParameters = bot.SESSION.parameters + } + + function finalize() { + tradingEngine = undefined + sessionParameters = undefined + } + + function openPosition(situationName) { + tradingEngine.current.position.status.value = 'Open' + tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value + tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value + + tradingEngine.current.position.situationName.value = situationName + + tradingEngine.current.position.stopLoss.stopLossStage.value = 'Open Stage' + tradingEngine.current.position.takeProfit.takeProfitStage.value = 'Open Stage' + tradingEngine.current.position.stopLoss.stopLossPhase.value = 0 + tradingEngine.current.position.takeProfit.takeProfitPhase.value = 0 + } + + function closePosition(exitType) { + tradingEngine.current.position.status.value = 'Closed' + tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.position.exitType.value = exitType + /* + Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure. + */ + const TRADING_ENGINE_MODULE = require('./TradingEngine.js') + let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) + + tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position) + } + + function resetPosition() { + tradingEngine.current.position.initialize(tradingEngine.current.position) + } + + function applyStopLossFormula(formulas, formulaId) { + tradingEngine.current.position.stopLoss.value = formulas.get(formulaId) + } + + function applyTakeProfitFormula(formulas, formulaId) { + tradingEngine.current.position.takeProfit.value = formulas.get(formulaId) + } + + function updateStopLoss(phase, stage) { + tradingEngine.current.position.stopLoss.stopLossPhase.value = phase + tradingEngine.current.position.stopLoss.stopLossStage.value = stage + } + + function updateTakeProfit(phase, stage) { + tradingEngine.current.position.takeProfit.takeProfitPhase.value = phase + tradingEngine.current.position.takeProfit.takeProfitStage.value = stage + } + + function updateSizeAndRate(size, rate) { + tradingEngine.current.position.size.value = size + tradingEngine.current.position.rate.value = rate + } + + function applySlippageToRate() { + let slippageAmount = tradingEngine.current.position.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100 + + if (bot.sessionAndMarketBaseAssetsAreEqual) { + tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value - slippageAmount + } else { + tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value + slippageAmount + } + } + + function preventStopLossDistortion() { + /* + Hit Point Validation + + This prevents misscalculations when a formula places the stop loss in this case way beyond the market price. + If we take the stop loss value at those situation would be a huge distortion of facts. + */ + if (bot.sessionAndMarketBaseAssetsAreEqual) { + if (tradingEngine.current.position.stopLoss.value < tradingEngine.current.candle.min.value) { + tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.min.value + } + } else { + if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.candle.max.value) { + tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.max.value + } + } + } + + function applySlippageToStopLoss() { + let slippedStopLoss = tradingEngine.current.position.stopLoss.value + + /* Apply the Slippage */ + let slippageAmount = slippedStopLoss * bot.SESSION.parameters.slippage.config.stopLoss / 100 + + if (bot.sessionAndMarketBaseAssetsAreEqual) { + slippedStopLoss = slippedStopLoss + slippageAmount + } else { + slippedStopLoss = slippedStopLoss - slippageAmount + } + + tradingEngine.current.position.endRate.value = slippedStopLoss + } + + function preventTakeProfitDistortion() { + /* + Hit Point Validation: + + This prevents misscalculations when a formula places the take profit in this case way beyond the market price. + If we take the stop loss value at those situation would be a huge distortion of facts. + */ + if (bot.sessionAndMarketBaseAssetsAreEqual) { + if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.candle.max.value) { + tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.max.value + } + } else { + if (tradingEngine.current.position.takeProfit.value < tradingEngine.current.candle.min.value) { + tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.min.value + } + } + } + + function applySlippageToTakeProfit() { + let slippedTakeProfit = tradingEngine.current.position.takeProfit.value + /* Apply the Slippage */ + let slippageAmount = slippedTakeProfit * bot.SESSION.parameters.slippage.config.takeProfit / 100 + + if (bot.sessionAndMarketBaseAssetsAreEqual) { + slippedTakeProfit = slippedTakeProfit + slippageAmount + } else { + slippedTakeProfit = slippedTakeProfit - slippageAmount + } + + tradingEngine.current.position.endRate.value = slippedTakeProfit + } + + function updateEnds() { + if (tradingEngine.current.position.status.value === 'Open') { + tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value + } + } + + function updateStatus() { + if (tradingEngine.current.position.status.value === 'Closed') { + resetPosition() + } + } + + function updateCounters() { + if (tradingEngine.current.position.status.value === 'Open') { + tradingEngine.current.position.positionCounters.periods.value++ + } + } + + function updateStatistics() { + if (bot.sessionAndMarketBaseAssetsAreEqual) { + tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.baseAsset.value + tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.value + } else { + tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.previous.balance.quotedAsset.value + tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.value + } + tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { + tradingEngine.current.position.positionStatistics.hitFail.value = 'HIT' + } else { + tradingEngine.current.position.positionStatistics.hitFail.value = 'FAIL' + } + } +} \ No newline at end of file diff --git a/TaskServer/LowFrequencyTrading/TradingProcess.js b/TaskServer/LowFrequencyTrading/TradingProcess.js new file mode 100644 index 000000000..eb23e6ae6 --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingProcess.js @@ -0,0 +1,767 @@ +exports.newTradingProcess = function newTradingProcess(bot, logger, UTILITIES, TRADING_OUTPUT_MODULE, USER_BOT_COMMONS) { + /* + This Module will load all the process data dependencies from files and send them downstream. + After execution, will save the time range and status report of the process. + */ + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + const MODULE_NAME = "Trading Process"; + const GMT_SECONDS = ':00.000 GMT+0000'; + + thisObject = { + initialize: initialize, + finalize: finalize, + start: start + }; + + let utilities = UTILITIES.newCloudUtilities(logger); + + let statusDependencies; + let dataDependenciesModule; + let dataFiles = new Map(); + let multiPeriodDataFiles = new Map(); + + let tradingOutputModule; + + const FILE_STORAGE = require('../FileStorage.js'); + let fileStorage = FILE_STORAGE.newFileStorage(logger); + + let processConfig; + + return thisObject; + + function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) { + try { + logger.fileName = MODULE_NAME; + logger.initialize(); + + statusDependencies = pStatusDependencies; + dataDependenciesModule = pDataDependencies; + processConfig = pProcessConfig; + + let TRADING_OUTPUT_MODULE = require("./TradingOutput") + tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE); + tradingOutputModule.initialize(callBackFunction); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function finalize() { + dataFiles = undefined + multiPeriodDataFiles = undefined + statusDependencies = undefined + dataDependenciesModule = undefined + tradingOutputModule.finalize() + tradingOutputModule = undefined + fileStorage = undefined + processConfig = undefined + thisObject = undefined + } + + function start(callBackFunction) { + try { + let currentTimeFrame + let currentTimeFrameLabel + let market = bot.market; + + /* Context Variables */ + + let contextVariables = { + lastFile: undefined, // Datetime of the last file files sucessfully produced by this process. + dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history. + dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process. + }; + + let previousDay; // Holds the date of the previous day relative to the processing date. + + getContextVariables(); + + function getContextVariables() { + try { + let thisReport; + let reportKey; + let statusReport; + + /* We are going to use the start date as beging of market date. */ + contextVariables.dateBeginOfMarket = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime) + + /* + Here we get the status report from the bot who knows which is the end of the market. + */ + statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point") + + if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + if (statusReport.status === "Status Report is corrupt.") { + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + thisReport = statusReport.file; + + if (thisReport.lastFile === undefined) { + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport = " + JSON.stringify(thisReport)); + + let customOK = { + result: global.CUSTOM_OK_RESPONSE.result, + message: "Dependency not ready." + } + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message); + callBackFunction(customOK); + return; + } + + contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf()); + + /* Validation that the data is not up-to-date. */ + if (bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS > contextVariables.dateEndOfMarket.valueOf() && bot.SESSION.type !== "Backtesting Session") { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> " + "Head of the market is @ " + contextVariables.dateEndOfMarket); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> You need to UPDATE your datasets in order to run a " + bot.SESSION.type) } + + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + + } + + /* Finally we get our own Status Report. */ + statusReport = statusDependencies.reportsByMainUtility.get("Self Reference") + + if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + if (statusReport.status === "Status Report is corrupt.") { + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + thisReport = statusReport.file; + bot.simulationState = thisReport.simulationState; + if (bot.simulationState === undefined) { bot.simulationState = {} } // This should happen only when there is no status report + + if (thisReport.lastFile !== undefined) { + if (bot.RESUME !== true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); } + startFromBegining(); + return; + } + contextVariables.lastFile = new Date(thisReport.lastFile); + processSingleFiles(); + return; + + } else { + /* + We are here becuse either: + 1. There is no status report + 2. There is no Last File (this happens on Market Files) + */ + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); } + startFromBegining(); + return; + } + + function startFromBegining() { + contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); } + processSingleFiles(); + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack); + if (err.message === "Cannot read property 'file' of undefined") { + logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. "); + logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys)); + } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function processSingleFiles() { + let dependencyIndex = 0; + dataFiles = new Map(); + dependencyLoopBody(); + + function dependencyLoopBody() { + let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; + let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; + getFile(); + + function getFile() { + if (datasetModule.node.config.codeName !== "Single-File") { + dependencyControlLoop(); + return + } + + if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) { + dependencyControlLoop(); + return + } + + let fileName = "Data.json"; + let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName; + datasetModule.getTextFile(filePath, fileName, onFileReceived); + + function onFileReceived(err, text) { + try { + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> text = " + text); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + if (err.message === 'File does not exist.') { + logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } else { + logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message); + callBackFunction(err); + } + return; + } + + let dataFile = JSON.parse(text); + dataFiles.set(dependency.id, dataFile); + + dependencyControlLoop(); + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + } + + function dependencyControlLoop() { + dependencyIndex++ + if (dependencyIndex < dataDependenciesModule.nodeArray.length) { + dependencyLoopBody() + } else { + let mapKey = "Single Files" + multiPeriodDataFiles.set(mapKey, dataFiles) + processMarketFiles() + } + } + } + + /* + We do market files first since if the simulation is run on daily files, there will be a loop to getch each daus files and we do not need + that loop to reload market files. + */ + function processMarketFiles() { + let n; + timeFramesLoop(); + + function timeFramesLoop() { + /* + We will iterate through all posible timeFrames. + */ + n = 0 // loop Variable representing each possible period as defined at the timeFrames array. + timeFramesLoopBody(); + } + + function timeFramesLoopBody() { + const timeFrame = global.marketFilesPeriods[n][0]; + const timeFrameLabel = global.marketFilesPeriods[n][1]; + + let dependencyIndex = 0; + dataFiles = new Map(); + + if (bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel) { + currentTimeFrame = global.marketFilesPeriods[n][0]; + currentTimeFrameLabel = global.marketFilesPeriods[n][1]; + } + dependencyLoopBody(); + + function dependencyLoopBody() { + let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; + let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; + getFile(); + + function getFile() { + if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") { + dependencyControlLoop(); + return + } + + if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { + if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { + dependencyControlLoop(); + return + } + } + + let fileName = "Data.json"; + let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel; + datasetModule.getTextFile(filePath, fileName, onFileReceived); + + function onFileReceived(err, text) { + try { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + if (err.message === 'File does not exist.') { + logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } else { + logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message); + callBackFunction(err); + } + return; + } + + let dataFile = JSON.parse(text); + let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record) + dataFiles.set(dependency.id, trimmedDataFile); + + dependencyControlLoop(); + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + } + + function dependencyControlLoop() { + dependencyIndex++; + if (dependencyIndex < dataDependenciesModule.nodeArray.length) { + dependencyLoopBody(); + } else { + let mapKey = global.marketFilesPeriods[n][1]; + multiPeriodDataFiles.set(mapKey, dataFiles) + timeFramesControlLoop(); + } + } + } + + function timeFramesControlLoop() { + n++; + if (n < global.marketFilesPeriods.length) { + timeFramesLoopBody(); + } else { + processDailyFiles(); + } + } + } + + function processDailyFiles() { + let n = 0; + if (currentTimeFrame) { + buildCharts() + return + } + bot.tradingProcessDate = new Date(contextVariables.lastFile.valueOf() - global.ONE_DAY_IN_MILISECONDS); // Go back one day to start well when we advance time at the begining of the loop. + advanceTime(); + + function advanceTime() { + bot.tradingProcessDate = new Date(bot.tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS); + previousDay = new Date(bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> bot.tradingProcessDate = " + bot.tradingProcessDate); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> previousDay = " + previousDay); } + + /* Validation that we are not going past the user defined end date. */ + if (bot.tradingProcessDate.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { + + const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); } + + bot.SESSION.stop(logText) + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + + /* Validation that we are not going past the head of the market. */ + if (bot.tradingProcessDate.valueOf() > contextVariables.dateEndOfMarket.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1) { + + const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); } + + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + + checkStopTaskGracefully(); + } + + function checkStopTaskGracefully() { + /* Validation that we dont need to stop. */ + if (global.STOP_TASK_GRACEFULLY === true) { + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + checkStopProcessing(); + } + + function checkStopProcessing() { + /* Validation that we dont need to stop. */ + if (bot.STOP_SESSION === true) { + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + timeFramesLoop(); + } + + function timeFramesLoop() { + /* Telling the world we are alive and doing well */ + let processingDate = bot.tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); + bot.processHeartBeat(processingDate) + /* + We will iterate through all posible timeFrames. + */ + n = 0 // loop Variable representing each possible period as defined at the timeFrames array. + timeFramesLoopBody(); + } + + function timeFramesLoopBody() { + const timeFrame = global.dailyFilePeriods[n][0]; + const timeFrameLabel = global.dailyFilePeriods[n][1]; + + if (processConfig.framework.validtimeFrames !== undefined) { + let validPeriod = false; + for (let i = 0; i < processConfig.framework.validtimeFrames.length; i++) { + let period = processConfig.framework.validtimeFrames[i]; + if (period === timeFrameLabel) { validPeriod = true } + } + if (validPeriod === false) { + timeFramesControlLoop(); + return; + } + } + + if (bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel) { + currentTimeFrame = global.dailyFilePeriods[n][0]; + currentTimeFrameLabel = global.dailyFilePeriods[n][1]; + } + + let dependencyIndex = 0; + dataFiles = new Map(); + dependencyLoopBody(); + + function dependencyLoopBody() { + let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; + let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; + let file + getFile() + + function getFile() { + if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") { + dependencyControlLoop(); + return + } + + if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { + if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { + dependencyControlLoop(); + return + } + } + + let dateForPath = bot.tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); + let filePath + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; + } else { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; + } + let fileName = "Data.json"; + + datasetModule.getTextFile(filePath, fileName, onFileReceived); + + function onFileReceived(err, text) { + try { + if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { + logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> Not Ok -> err = " + err.code); + callBackFunction(err); + return; + } + + file = JSON.parse(text); + dataFiles.set(dependency.id, file); + dependencyControlLoop(); + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + } + + function dependencyControlLoop() { + dependencyIndex++; + + if (dependencyIndex < dataDependenciesModule.nodeArray.length) { + dependencyLoopBody(); + } else { + let mapKey = global.dailyFilePeriods[n][1]; + multiPeriodDataFiles.set(mapKey, dataFiles) + timeFramesControlLoop(); + } + } + } + + function timeFramesControlLoop() { + n++; + if (n < global.dailyFilePeriods.length) { + timeFramesLoopBody(); + } else { + n = 0; + if (currentTimeFrame !== undefined) { + buildCharts(advanceTime); + } else { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesControlLoop -> Time Frame not Recognized. Can not continue."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + } + + function buildCharts(advanceTime) { + const COMMONS = require('../Commons.js') + let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE) + + let chart = {} + let mainDependency = {} + + /* The first phase here is about checking that we have everything we need at the definition level. */ + let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } + + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + + /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + let timeFrameLabel = marketFilesPeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) + let products = {} + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame) + + let propertyName = 'at' + timeFrameLabel.replace('-', '') + chart[propertyName] = products + } + } + + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + let timeFrameLabel = global.dailyFilePeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) + let products = {} + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame) + + let propertyName = 'at' + timeFrameLabel.replace('-', '') + chart[propertyName] = products + } + } + + /* Single Files */ + let dataFiles = multiPeriodDataFiles.get('Single Files') + let products = {} + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame) + + let propertyName = 'atAnyTimeFrame' + chart[propertyName] = products + } + + tradingOutputModule.start( + chart, + currentTimeFrame, + currentTimeFrameLabel, + bot.tradingProcessDate, + onOutputGenerated); + + function onOutputGenerated(err) { + try { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + callBackFunction(err); + return; + } + + bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution. + + if (currentTimeFrame > global.dailyFilePeriods[0][0]) { + writeMarketStatusReport(onMarketStatusReport) + } else { + writeDataRanges(currentTimeFrameLabel, onWritten); + } + + function onWritten(err) { + try { + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack); + callBackFunction(err); + return; + } + + writeDailyStatusReport(bot.tradingProcessDate, onDailyStatusReport); + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function onDailyStatusReport() { + advanceTime(); + } + + function onMarketStatusReport() { + /* This is where we check if we need reached the user defined end datetime. */ + const now = new Date() + if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { + const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onMarketStatusReport -> " + logText); } + + bot.SESSION.stop('because it just finished.') + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + if (bot.SESSION.type === 'Backtesting Session') { + bot.SESSION.stop('Backtesting Session over Market Files Finished.') + } + callBackFunction(global.DEFAULT_OK_RESPONSE); + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + + function writeDataRanges(currentTimeFrameLabel, callBack) { + let outputDatasetIndex = -1; + controlLoop(); + + function productLoopBody() { + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel, controlLoop); + } + + function controlLoop() { + outputDatasetIndex++; + if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { + productLoopBody(); + } else { + callBack(global.DEFAULT_OK_RESPONSE); + } + } + } + + function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel, callBack) { + let dataRange = { + begin: pBegin.valueOf(), + end: pEnd.valueOf() + global.ONE_DAY_IN_MILISECONDS + }; + + let fileContent = JSON.stringify(dataRange); + let fileName = '/Data.Range.json'; + let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName; + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack); + callBack(err); + return; + } + + if (LOG_FILE_CONTENT === true) { + logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent); + } + + let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let event = { + dateRange: dataRange + } + + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) + + callBack(global.DEFAULT_OK_RESPONSE); + } + } + + function writeDailyStatusReport(lastFileDate, callBack) { + let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName + let thisReport = statusDependencies.statusReports.get(reportKey); + + thisReport.file.lastExecution = bot.currentDaytime; + thisReport.file.lastFile = lastFileDate; + thisReport.file.simulationState = bot.simulationState; + thisReport.save(callBack); + } + + function writeMarketStatusReport(callBack) { + let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName + let thisReport = statusDependencies.statusReports.get(reportKey); + + thisReport.file.lastExecution = bot.processDatetime; + thisReport.file.simulationState = bot.simulationState; + + logger.newInternalLoop(bot.codeName, bot.process, bot.tradingProcessDate); + thisReport.save(callBack); + } + + function trimDataFile(dataFile, recordDefinition) { + /* + Here we will discard all the records in a file that are outside of the current time range. + */ + let beginIndex + let endIndex + let result = [] + for (let i = 0; i < recordDefinition.properties.length; i++) { + let property = recordDefinition.properties[i] + if (property.config.codeName === 'begin') { + beginIndex = i + } + if (property.config.codeName === 'end') { + endIndex = i + } + } + for (let i = 0; i < dataFile.length; i++) { + let dataRecord = dataFile[i] + let begin = dataRecord[beginIndex] + let end = dataRecord[endIndex] + if (end < bot.SESSION.parameters.timeRange.config.initialDatetime) { continue } + if (begin > bot.SESSION.parameters.timeRange.config.finalDatetime) { continue } + result.push(dataRecord) + } + return result + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function pad(str, max) { + str = str.toString(); + return str.length < max ? pad(" " + str, max) : str; + } +}; diff --git a/TaskServer/LowFrequencyTrading/TradingRecords.js b/TaskServer/LowFrequencyTrading/TradingRecords.js new file mode 100644 index 000000000..f15526649 --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingRecords.js @@ -0,0 +1,264 @@ +exports.newTradingRecords = function newTradingRecords(bot, logger) { + /* + This module facilitates the appending of records to the output of the process. + */ + const MODULE_NAME = 'Trading Records' + + let thisObject = { + appendRecords: appendRecords, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + let tradingSystem + let sessionParameters + let outputDatasetsMap + + return thisObject + + function initialize(pOutputDatasetsMap) { + tradingEngine = bot.simulationState.tradingEngine + tradingSystem = bot.simulationState.tradingSystem + sessionParameters = bot.SESSION.parameters + outputDatasetsMap = pOutputDatasetsMap // These are the files turned into arrays, stored in a Map by Product codeName. + } + + function finalize() { + tradingEngine = undefined + tradingSystem = undefined + sessionParameters = undefined + outputDatasetsMap = undefined + } + + function appendRecords() { + /* + Here we add records to the output files. At the product config property nodePath + we have a pointer to the node that have the information we need to extract. + Later, based on the product record definition we will extract each individual value. + */ + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let dataset = outputDatasetNode.referenceParent + let product = dataset.parentNode + let outputDatasetArray = outputDatasetsMap.get(product.config.codeName) + + if (bot.processingDailyFiles === true && dataset.config.type === 'Daily Files') { + persistRecord() + } + + if (bot.processingDailyFiles === false && dataset.config.type === 'Market Files') { + persistRecord() + } + + function persistRecord() { + pruneOutputFile(product, outputDatasetArray) + let record = scanRecordDefinition(product) + + if (product.config.saveAsObjects === true) { + /* For saving objects we need to take care of a different set of rules. */ + for (let j = 0; j < product.record.properties.length; j++) { + let recordProperty = product.record.properties[j] + if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) { + let propertyValue = record[j] + /* + We check that the existing file does not contain at the end a record that + does not signal that the object is closed. + If it does, we replace it with the new one until at some point it will signal + does the object is closed. + */ + let lastRecord = outputDatasetArray[outputDatasetArray.length - 1] + if (lastRecord !== undefined) { + let lastRecordValue = lastRecord[j] + if (lastRecordValue !== product.config.propertyValueThatClosesObject) { + outputDatasetArray.splice(outputDatasetArray.length - 1, 1) + } + } + if (bot.processingDailyFiles) { + /* + When dealing with Daily Files, we need to avoid to write an open object at the last 'candle' of the day, + since the object will be duplicated on the next day. How do we know we are positioned at the last candle + of the day? Easy: the end of the candle must be 1 millisecod before the next day. That happens at any + time frame. + */ + let currentDay = new Date(tradingEngine.current.candle.end.value) + let nextDay = new Date(tradingEngine.current.candle.end.value + 1) + if (currentDay.getUTCDate() !== nextDay.getUTCDate()) { + /* + We will save the object only if it is closed, becasuse we are at the last candle of the day. + */ + if (propertyValue === product.config.propertyValueThatClosesObject) { + outputDatasetArray.push(record) + } + } else { + /* + When we are not at the end of the day, we will save the object normally, like in market files. + */ + if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { + outputDatasetArray.push(record) + } + } + } + else { + /* + For Market Files we will add a record everytime that proeprty value does not match this + */ + if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { + outputDatasetArray.push(record) + } + } + break + } + } + } else { + /* When we are not dealing with objects, we add every record to the existing file. */ + outputDatasetArray.push(record) + } + } + } + + function scanRecordDefinition(product) { + /* + The Product Root Node is the root of the node hiriarchy branch that we need to look at + in order to fnd the node where we are going to extract the value. + */ + let productRootNode = eval(product.config.nodePath) + let record = [] + for (let j = 0; j < product.record.properties.length; j++) { + let recordProperty = product.record.properties[j] + /* + The Property Root Node is the Root of the Hiriarchy branch we must find in order + to get the node where we are going to extract the value. Initially + we point it to the Product Root Node, because that is the default in case + a property does not have at its configuration a different nodePath configured + pointing to an specific Root for the property. + */ + let propertyRootNode = productRootNode + /* + If we find at the configuration a nodePath, then we take this path to find + the Root Node specifically for this property only. + */ + if (recordProperty.config.nodePath !== undefined) { + propertyRootNode = eval(recordProperty.config.nodePath) + } + /* + The Target Node is the node from where we are going to exctract the value. + We will use the codeName of the Record Property to match it with + the any of the properties of the Root Node to get the Target Node. + */ + let targetNode = propertyRootNode[recordProperty.config.codeName] + /* + If the codeName of the Record Property can not match the name of the property at + the target node, the user can explicitly specify the property name at the configuration, + and in those cases we need to use that. This happens when there are many Record Properties + pointing to the same property at the Target Node. + */ + if (recordProperty.config.childProperty !== undefined) { + targetNode = propertyRootNode[recordProperty.config.childProperty] + } + /* + It can happen that intead of having a Node in targetNode what we have is an + array of nodes. We need to pick one of the elements of the array and for that + we use the Index value we find at the configuration of the Record Property. + */ + if (recordProperty.config.index !== undefined) { + targetNode = targetNode[recordProperty.config.index] + } + /* + By Default the value is extracted from the value property of the Target Node. + But it might happen the the Target Node does not exist for example when there is an Array + of Nodes defined in the Record Properties but not all of them exist at the Root Node. + We filter out those cases by not extracting the value from the value property. + */ + let value = 0 // This is a default value, since we do not want null in files because it breakes JSON format. + if (targetNode !== undefined) { + if (targetNode.type !== undefined) { + /* + In this case the Target Node is really a node (since it has a type), so we extract the value + from its value property. + */ + value = targetNode.value + + if (recordProperty.config.decimals !== undefined) { + value = Number(value.toFixed(recordProperty.config.decimals)) + } + } else { + /* + In this case the Target Node is not really node, but the value itself. Se we return this as the + value of the Record Property. + */ + value = targetNode + } + } + if (recordProperty.config.isString !== true && Array.isArray(value) !== true) { + value = safeNumericValue(value) + } + if (recordProperty.config.isString === true && Array.isArray(value) !== true) { + value = safeStringValue(value) + } + record.push(value) + } + return record + } + } + + function safeNumericValue(value) { + /* + The purpose of this function is to check that value variable does not have a value that + will later break the JSON format of files where this is going to be stored at. + */ + if (value === Infinity) { + value = Number.MAX_SAFE_INTEGER + } + if (value === undefined) { + value = 0 + } + if (value === null) { + value = 0 + } + if (isNaN(value)) { + value = 0 + } + return value + } + + function safeStringValue(value) { + /* + The purpose of this function is to check that value variable does not have a value that + will later break the JSON format of files where this is going to be stored at. + */ + if (value === undefined) { + value = '' + } + if (value === null) { + value = '' + } + return value + } + + function pruneOutputFile(product, outputFile) { + if (outputFile.isPrunned === true) { return } + /* + When a session is resumed, we will be potentially reading output files belonging to a previous session execution. + For that reason we need to prune all the records that are beyond the current candle. We do not delete everything + because we might be resuming a stopped session, which is fine. + */ + for (let i = 0; i < outputFile.length; i++) { + let record = outputFile[i] + + for (let j = 0; j < product.record.properties.length; j++) { + let recordProperty = product.record.properties[j] + if (recordProperty.config.codeName === 'end') { + let end = record[j] + if (end >= tradingEngine.current.candle.end.value) { + outputFile.splice(i, 1) + pruneOutputFile(product, outputFile) + return + } + } + } + } + outputFile.isPrunned = true + } +} \ No newline at end of file diff --git a/TaskServer/LowFrequencyTrading/TradingSimulation.js b/TaskServer/LowFrequencyTrading/TradingSimulation.js new file mode 100644 index 000000000..208ff2c4c --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingSimulation.js @@ -0,0 +1,452 @@ +exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) { + /* + This Module represents the trading simulacion. Escentially a loop through a set of candles and + the execution at each loop cycle of the Trading System Protocol. + */ + const FULL_LOG = true + const MODULE_NAME = 'Trading Simulation -> ' + bot.SESSION.name + + let thisObject = { + finalize: finalize, + runSimulation: runSimulation + } + + let utilities = UTILITIES.newCloudUtilities(bot, logger) + + return thisObject + + function finalize() { + thisObject = undefined + } + + function runSimulation( + chart, + outputDatasetsMap, + callback, + callBackFunction) { + try { + + let tradingSystem = bot.simulationState.tradingSystem + let tradingEngine = bot.simulationState.tradingEngine + let sessionParameters = bot.SESSION.parameters + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) } + + /* Stop Loss Management TODO */ + const MIN_STOP_LOSS_VALUE = 0.0000000001 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result. + const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER + + /* Take Profit Management TODO */ + const MIN_TAKE_PROFIT_VALUE = 0.0000000001 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result. + const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER + + /* Variables to know when we need to open or close a position. */ + let triggeringOn + let triggeringOff + let takingPosition + let closingPosition + + /* These are the Modules we will need to run the Simulation */ + const EXCHANGE_API = require('../ExchangeAPI'); + exchangeAPI = EXCHANGE_API.newExchangeAPI(logger, bot); + exchangeAPI.initialize(); + + const TRADING_ENGINE_MODULE = require('./TradingEngine.js') + let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) + tradingEngineModule.initialize() + + const TRADING_ENGINE_EXECUTION = require('./TradingExecution.js') + let tradingExecutionModule = TRADING_ENGINE_EXECUTION.newTradingExecution(bot, logger) + tradingExecutionModule.initialize() + + const TRADING_RECORDS_MODULE = require('./TradingRecords.js') + let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger) + tradingRecordsModule.initialize(outputDatasetsMap) + + const TRADING_SYSTEM_MODULE = require('./TradingSystem.js') + let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger) + tradingSystemModule.initialize() + + /* Setting up the candles array: The whole simulation is based on the array of candles at the time-frame defined at the session parameters. */ + let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '') + let candles = chart[propertyName].candles + + /* Variables needed for heartbeat functionality */ + let heartBeatDate + let previousHeartBeatDate + + /* This determines where we are standing */ + bot.sessionAndMarketBaseAssetsAreEqual = (sessionParameters.sessionBaseAsset.referenceParent.referenceParent.config.codeName === bot.market.baseAsset) + + initializeLoop() + + function initializeLoop() { + if (bot.FIRST_EXECUTION === false && tradingEngine.last.candle !== undefined) { + /* Estimate the Initial Candle based on the last candle saved at tradingEngine */ + let firstBegin = candles[0].begin + let lastBegin = tradingEngine.last.candle.begin + let diff = lastBegin - firstBegin + let amount = diff / sessionParameters.timeFrame.config.value + + tradingEngine.current.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle + if (tradingEngine.current.candle.index.value < 0 || tradingEngine.current.candle.index.value > candles.length - 1) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.') + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.candle.index.value = ' + tradingEngine.current.candle.index.value) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount) + + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } + } else { + /* Estimate Initial Candle based on the timeRage configured for the session. */ + let firstEnd = candles[0].end + let targetEnd = sessionParameters.timeRange.config.initialDatetime + let diff = targetEnd - firstEnd + let amount = diff / sessionParameters.timeFrame.config.value + + tradingEngine.current.candle.index.value = Math.trunc(amount) + if (tradingEngine.current.candle.index.value < 0) { tradingEngine.current.candle.index.value = 0 } + if (tradingEngine.current.candle.index.value > candles.length - 1) { + /* + This will happen when the sessionParameters.timeRange.config.initialDatetime is beyond the last candle available, + meaning that the dataSet needs to be updated with more up-to-date data. + */ + tradingEngine.current.candle.index.value = candles.length - 1 + } + } + + loop() + } + + function loop() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.candle.index.value) } + let candle = candles[tradingEngine.current.candle.index.value] // This is the current candle the Simulation is working at. + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toLocaleString()) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) } + + tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading engine data structure to make it available to anyone, including conditions and formulas. + + if (checkIfWeAreAtTheHeadOfTheMarket() === false) { + afterLoop() + return + } + + if (checkMinimunAndMaximunBalance() === false) { + afterLoop() + return + } + + if (checkInitialAndFinalDatetime() === false) { + controlLoop() + return + } + + heartBeat() + positionChartAtCurrentCandle() + tradingSystemModule.reset(chart) + tradingSystemModule.mantainStrategies() + tradingSystemModule.mantainPositions() + + tradingEngineModule.updateEpisodeCountersAndStatistics() + /* + The next step is about evaluationg Conditions and Formulas of the Trading System. + */ + tradingSystemModule.evalConditions() + tradingSystemModule.evalFormulas() + /* + Next we check if we will be triggering on, off or taking position. + */ + triggeringOn = tradingSystemModule.checkTriggerOn() + triggeringOff = tradingSystemModule.checkTriggerOff() + takingPosition = tradingSystemModule.checkTakePosition() + + /* Stop Loss Management */ + if (takingPosition !== true) { + tradingSystemModule.checkStopPhasesEvents() + tradingSystemModule.calculateStopLoss() + } + + /* Take Profit Management */ + if (takingPosition !== true) { + tradingSystemModule.checkTakeProfitPhaseEvents() + tradingSystemModule.calculateTakeProfit() + } + + tradingEngineModule.updateDistanceToEventsCounters() + + /* Checking if Stop or Take Profit were hit */ + if (takingPosition !== true) { + closingPosition = tradingSystemModule.checkStopLossOrTakeProfitWasHit() + } + + /* Taking a Position */ + if (takingPosition === true) { + tradingSystemModule.getReadyToTakePosition() + /* + Whem we calculated all the formulas at the begining of the loop, we didn't know + we were going to take position in this same loop cycle. Some might formulas depends on + calculations that are triggered only when we are taking position. For that reason + we will calculate all formulas again, and after that the Stop Loss and Take Profit. + */ + tradingSystemModule.evalFormulas() + tradingSystemModule.calculateTakeProfit() + tradingSystemModule.calculateStopLoss() + + tradingExecutionModule.takePosition() + tradingSystemModule.takePosition() + takingPosition = false + } + + /* Closing a Position */ + if (closingPosition === true) { + tradingSystemModule.getReadyToClosePosition() + tradingExecutionModule.closePosition() + tradingSystemModule.closePosition() + closingPosition = false + } + + /* After a position was closed, we need to close the strategy. */ + tradingSystemModule.exitStrategyAfterPosition() + tradingRecordsModule.appendRecords() + + controlLoop() + } + + function controlLoop() { + /* Checking if we should continue processing this loop or not. */ + if (bot.STOP_SESSION === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } + console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') + afterLoop() + return + } + + if (global.STOP_TASK_GRACEFULLY === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } + console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') + afterLoop() + return + } + + tradingEngine.current.candle.index.value++ + if (tradingEngine.current.candle.index.value < candles.length) { + setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. + } else { + afterLoop() + } + } + + function afterLoop() { + tradingEngineModule.finalize() + tradingExecutionModule.finalize() + tradingSystemModule.finalize() + tradingRecordsModule.finalize() + + callback() + } + + function heartBeat() { + /* We will produce a simulation level heartbeat in order to inform the user this is running. */ + heartBeatDate = new Date(Math.trunc(tradingEngine.current.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) + if (heartBeatDate.valueOf() !== previousHeartBeatDate) { + let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.candle.index.value + ' @ ' + processingDate) } + + /* Telling the world we are alive and doing well */ + let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime) + let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime) + + let currentDateString = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) + let currentDate = new Date(heartBeatDate) + let percentage = global.getPercentage(fromDate, currentDate, lastDate) + bot.processHeartBeat(currentDateString, percentage) + + if (global.areEqualDates(currentDate, new Date()) === false) { + logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage) + } + } + previousHeartBeatDate = heartBeatDate.valueOf() + } + + function positionChartAtCurrentCandle() { + /* + In conditions and Formulas, we want users to have an easy sintax to refer to indicators. In order to achieve that, we need the user to have + easy access to the current candle for instance, or the current bollinger band, meaning the one the Simulation is currently standing at. + For that reason we do the following processing, to have at the chart data structure the current objects of each indicator / time frame. + */ + let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + + /* Finding the Current Element on Market Files */ + if (bot.processingDailyFiles) { + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + let mapKey = dailyFilePeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', '') + let thisChart = chart[propertyName] + + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName + let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName) + thisChart[singularVariableName] = currentElement + } + } + } + + /* Finding the Current Element on Market Files */ + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + let mapKey = marketFilesPeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', '') + let thisChart = chart[propertyName] + + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName + let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName) + thisChart[singularVariableName] = currentElement + } + } + + /* Finding the Current Element At Single Files*/ + let propertyName = 'atAnyTimeFrame' + let thisChart = chart[propertyName] + + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.config.codeName !== 'Single-File') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName + let elementArray = thisChart[pluralVariableName] + let currentElement + if (elementArray !== undefined) { + currentElement = elementArray[elementArray.length - 1] + } + thisChart[singularVariableName] = currentElement + } + } + + function getElement(pArray, datasetName) { + if (pArray === undefined) { return } + try { + let element + for (let i = 0; i < pArray.length; i++) { + element = pArray[i] + + if (tradingEngine.current.candle.end.value === element.end) { // when there is an exact match at the end we take that element + return element + } else { + if ( + i > 0 && + element.end > tradingEngine.current.candle.end.value + ) { + let previousElement = pArray[i - 1] + if (previousElement.end < tradingEngine.current.candle.end.value) { + return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element. + } else { + return + } + } + } + } + return + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function checkIfWeAreAtTheHeadOfTheMarket() { + /* + We skip the candle at the head of the market because it has not closed yet. The procedure to determine if we are at the head of the market is different + when we are processing Market Files than when we are processing Daily Files. TODO Check that the procedure is good for Beta 6 + */ + if (bot.processingDailyFiles) { // We are processing Daily Files + let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value + if (tradingEngine.current.candle.index.value === candles.length - 1) { + if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') } + return false + /* + Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators. + if we skip these two periods, then there will be a hole in the file since the last period will be missing. + */ + } + } + } else { // We are processing Market Files + if (tradingEngine.current.candle.index.value === candles.length - 1) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') } + return false + } + } + return true + } + + function checkInitialAndFinalDatetime() { + /* Here we check that the current candle is not outside of user-defined time range at the session parameters.*/ + if (tradingEngine.current.candle.end.value < sessionParameters.timeRange.config.initialDatetime) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') } + return false + } + if (tradingEngine.current.candle.begin.value > sessionParameters.timeRange.config.finalDatetime) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') } + return false + } + return true + } + + function checkMinimunAndMaximunBalance() { + /* Checks for Minimun and Maximun Balance. We do the check while not inside any strategy only. */ + if ( + tradingEngine.current.strategy.stageType.value === 'No Stage' && + tradingEngine.current.strategy.index.value === -1 + ) { + /* + First thing to do is to check if we are below the minimum balance, or above the maximun balance. + If we are, we are not gooing to trigger on again. + */ + let minimumBalance + let maximumBalance + let balance + + if (bot.sessionAndMarketBaseAssetsAreEqual) { + balance = tradingEngine.current.balance.baseAsset.value + minimumBalance = sessionParameters.sessionBaseAsset.config.minimumBalance + maximumBalance = sessionParameters.sessionBaseAsset.config.maximumBalance + } else { + balance = tradingEngine.current.balance.quotedAsset.value + minimumBalance = sessionParameters.sessionQuotedAsset.config.minimumBalance + maximumBalance = sessionParameters.sessionQuotedAsset.config.maximumBalance + } + + let stopRunningDate = new Date(tradingEngine.current.candle.begin.value) + if (balance <= minimumBalance) { + tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) } + return false + } + + if (balance >= maximumBalance) { + tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) } + return false + } + } + return true + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } +} + diff --git a/TaskServer/LowFrequencyTrading/TradingStrategy.js b/TaskServer/LowFrequencyTrading/TradingStrategy.js new file mode 100644 index 000000000..a07627cd1 --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingStrategy.js @@ -0,0 +1,83 @@ +exports.newTradingStrategy = function newTradingStrategy(bot, logger) { + /* + This module packages all functions related to Strategies. + */ + const MODULE_NAME = 'Trading Strategy' + let thisObject = { + openStrategy: openStrategy, + closeStrategy: closeStrategy, + updateEnds: updateEnds, + updateStageType: updateStageType, + updateStatus: updateStatus, + updateCounters: updateCounters, + resetStrategy: resetStrategy, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + + return thisObject + + function initialize() { + tradingEngine = bot.simulationState.tradingEngine + } + + function finalize() { + tradingEngine = undefined + } + + function openStrategy(stageType, index, situationName, strategyName) { + tradingEngine.current.strategy.status.value = 'Open' + tradingEngine.current.strategy.begin.value = tradingEngine.current.candle.begin.value + tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategy.beginRate.value = tradingEngine.current.candle.min.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value + + tradingEngine.current.strategy.stageType.value = stageType + tradingEngine.current.strategy.index.value = index + tradingEngine.current.strategy.situationName.value = situationName + tradingEngine.current.strategy.strategyName.value = strategyName + } + + function closeStrategy(exitType) { + tradingEngine.current.strategy.status.value = 'Closed' + tradingEngine.current.strategy.exitType.value = exitType + tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value + /* + Now that the strategy is closed, it is the right time to move this strategy from current to last at the Trading Engine data structure. + */ + const TRADING_ENGINE_MODULE = require('./TradingEngine.js') + let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) + + tradingEngineModule.cloneValues(tradingEngine.current.strategy, tradingEngine.last.strategy) + } + + function resetStrategy() { + tradingEngine.current.strategy.initialize(tradingEngine.current.strategy) + } + + function updateEnds() { + if (tradingEngine.current.strategy.status.value === 'Open') { + tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value + } + } + + function updateStageType(stageType) { + tradingEngine.current.strategy.stageType.value = stageType + } + + function updateStatus() { + if (tradingEngine.current.strategy.status.value === 'Closed') { + resetStrategy() + } + } + + function updateCounters() { + if (tradingEngine.current.strategy.status.value === 'Open') { + tradingEngine.current.strategy.strategyCounters.periods.value++ + } + } +} \ No newline at end of file diff --git a/TaskServer/LowFrequencyTrading/TradingSystem.js b/TaskServer/LowFrequencyTrading/TradingSystem.js new file mode 100644 index 000000000..0aaa58865 --- /dev/null +++ b/TaskServer/LowFrequencyTrading/TradingSystem.js @@ -0,0 +1,963 @@ +exports.newTradingSystem = function newTradingSystem(bot, logger) { + /* + The Trading System is the user defined set of rules compliant with the Trading Protocol that + defines the trading logic to be applied during each cycle of the Simulation. + */ + const MODULE_NAME = 'Trading System' + let thisObject = { + reset: reset, + mantainStrategies: mantainStrategies, + mantainPositions: mantainPositions, + evalConditions: evalConditions, + evalFormulas: evalFormulas, + checkTriggerOn: checkTriggerOn, + checkTriggerOff: checkTriggerOff, + checkTakePosition: checkTakePosition, + checkStopPhasesEvents: checkStopPhasesEvents, + calculateStopLoss: calculateStopLoss, + checkTakeProfitPhaseEvents: checkTakeProfitPhaseEvents, + calculateTakeProfit: calculateTakeProfit, + checkStopLossOrTakeProfitWasHit: checkStopLossOrTakeProfitWasHit, + getReadyToTakePosition: getReadyToTakePosition, + takePosition: takePosition, + getReadyToClosePosition: getReadyToClosePosition, + closePosition: closePosition, + exitStrategyAfterPosition: exitStrategyAfterPosition, + getPositionSize: getPositionSize, + getPositionRate: getPositionRate, + initialize: initialize, + finalize: finalize + } + + let chart + + let tradingSystem + let tradingEngine + let sessionParameters + + let conditions = new Map() + let formulas = new Map() + + const TRADING_STRATEGY_MODULE = require('./TradingStrategy.js') + let tradingStrategyModule = TRADING_STRATEGY_MODULE.newTradingStrategy(bot, logger) + + const TRADING_POSITION_MODULE = require('./TradingPosition.js') + let tradingPositionModule = TRADING_POSITION_MODULE.newTradingPosition(bot, logger) + + const ANNOUNCEMENTS_MODULE = require('./Announcements.js') + let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger) + + const SNAPSHOTS_MODULE = require('./Snapshots.js') + let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger) + + return thisObject + + function initialize() { + tradingSystem = bot.simulationState.tradingSystem + tradingEngine = bot.simulationState.tradingEngine + sessionParameters = bot.SESSION.parameters + + tradingStrategyModule.initialize() + tradingPositionModule.initialize() + announcementsModule.initialize() + snapshotsModule.initialize() + } + + function finalize() { + tradingStrategyModule.finalize() + tradingStrategyModule = undefined + + tradingPositionModule.finalize() + tradingPositionModule = undefined + + announcementsModule.finalize() + announcementsModule = undefined + + snapshotsModule.finalize() + snapshotsModule = undefined + + chart = undefined + + conditions = undefined + formulas = undefined + + tradingSystem.highlights = undefined + tradingSystem.errors = undefined + tradingSystem.values = undefined + tradingSystem.status = undefined + tradingSystem.progress = undefined + tradingSystem.running = undefined + + tradingSystem = undefined + tradingEngine = undefined + sessionParameters = undefined + } + + function reset(pChart) { + /* This function helps reset data structures at every cycle of the simulation loop/=. */ + chart = pChart // We need chat to be a local object accessible from conditions and formulas. + snapshotsModule.reset(chart) + + tradingSystem.highlights = [] + tradingSystem.errors = [] + tradingSystem.values = [] + tradingSystem.status = [] + tradingSystem.progress = [] + tradingSystem.running = [] + tradingSystem.announcements = [] + } + + function mantainStrategies() { + tradingStrategyModule.updateStatus() + tradingStrategyModule.updateCounters() + tradingStrategyModule.updateEnds() + } + + function mantainPositions() { + tradingPositionModule.updateStatus() + tradingPositionModule.updateCounters() + tradingPositionModule.updateEnds() + } + + function evalConditions() { + evalNode(bot.simulationState.tradingSystem, 'Conditions') + } + + function evalFormulas() { + evalNode(bot.simulationState.tradingSystem, 'Formulas') + } + + function evalNode(node, evaluating) { + if (node === undefined) { return } + + /* Here we check if there is a codition to be evaluated */ + if (node.type === 'Condition' && evaluating === 'Conditions') { + /* We will eval this condition */ + evalCondition(node) + } + + /* Here we check if there is a formula to be evaluated */ + if (node.type === 'Formula' && evaluating === 'Formulas') { + if (node.code !== undefined) { + /* We will eval this formula */ + evalFormula(node) + } + } + + /* Now we go down through all this node children */ + let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + evalNode(node[property.name], evaluating) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + for (let m = 0; m < nodePropertyArray.length; m++) { + evalNode(nodePropertyArray[m], evaluating) + } + } + break + } + } + } + } + } + + function evalCondition(node) { + let value + let error + /* + The code can be at the condition node if it was done with the Conditions Editor, or it can also be + at a Javascript Code node. If there is a Javascript object we will give it preference and take the code + from there. Otherwise we will take the code from the Condition node. + */ + let code = node.code + if (node.javascriptCode !== undefined) { + if (node.javascriptCode.code !== undefined) { + code = node.javascriptCode.code + } + } + + try { + logger.write(MODULE_NAME, '[INFO] evalCondition -> code = ' + code) + value = eval(code) + } catch (err) { + /* + One possible error is that the conditions references a .previous that is undefined. This + will not be considered an error. + */ + value = false + + if (code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || + code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1 + ) { + /* + We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. + */ + } else { + error = err.message + } + } + + conditions.set(node.id, value) + + if (value === true) { + tradingSystem.highlights.push(node.id) + } + if (error !== undefined) { + tradingSystem.errors.push([node.id, error]) + } + if (value !== undefined) { + tradingSystem.values.push([node.id, value]) + } + + logger.write(MODULE_NAME, '[INFO] evalCondition -> value = ' + value) + logger.write(MODULE_NAME, '[INFO] evalCondition -> error = ' + error) + } + + function evalFormula(node) { + let value + let error + + try { + logger.write(MODULE_NAME, '[INFO] evalFormula -> code = ' + node.code) + value = eval(node.code) + } catch (err) { + /* + One possible error is that the formula references a .previous that is undefined. This + will not be considered an error. + */ + value = 0 + + if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || + node.code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1 + ) { + /* + We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. + */ + } else { + error = err.message + } + } + + formulas.set(node.id, value) + + if (error !== undefined) { + tradingSystem.errors.push([node.id, error]) + } + if (value !== undefined) { + tradingSystem.values.push([node.id, value]) + } + + logger.write(MODULE_NAME, '[INFO] evalFormula -> value = ' + value) + logger.write(MODULE_NAME, '[INFO] evalFormula -> error = ' + error) + } + + function checkTriggerOn() { + if ( + tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue + ) { + /* + To pick a new strategy we will check that any of the situations of the trigger on is true. + Once we enter into one strategy, we will ignore market conditions for others. However there is also + a strategy trigger off which can be hit before taking a position. If hit, we would + be outside a strategy again and looking for the conditions to enter all over again. + */ + + for (let j = 0; j < tradingSystem.tradingStrategies.length; j++) { + if ( // If a strategy was already picked during the loop, we exit the loop + tradingEngine.current.strategy.index.value !== tradingEngine.current.strategy.index.config.initialValue + ) { continue } + + let strategy = tradingSystem.tradingStrategies[j] + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.triggerOn !== undefined) { + for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { + let situation = triggerStage.triggerOn.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let value = false + if (conditions.get(condition.id) !== undefined) { + value = conditions.get(condition.id) + } + if (value !== true) { passed = false } + } + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(triggerStage.triggerOn.id) + tradingSystem.highlights.push(triggerStage.id) + + tradingStrategyModule.openStrategy('Trigger Stage', j, situation.name, strategy.name) + + tradingEngine.current.distanceToEvent.triggerOn.value = 1 + + announcementsModule.makeAnnoucements(triggerStage.triggerOn) + announcementsModule.makeAnnoucements(triggerStage) + + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.strategy === true) { + snapshotsModule.strategyEntry() + } + } + } + + logger.write(MODULE_NAME, '[INFO] checkTriggerOn -> Entering into Strategy: ' + strategy.name) + return true // This Means that we have just met the conditions to trigger on. + } + } + } + } + } + } + return false // This Means that we have not met the conditions to trigger on. + } + + function checkTriggerOff() { + if (tradingEngine.current.strategy.stageType.value === 'Trigger Stage') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.triggerOff !== undefined) { + for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { + let situation = triggerStage.triggerOff.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let value = false + if (conditions.get(condition.id) !== undefined) { + value = conditions.get(condition.id) + } + if (value !== true) { passed = false } + } + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(triggerStage.triggerOff.id) + tradingSystem.highlights.push(triggerStage.id) + + tradingStrategyModule.closeStrategy('Trigger Off') + + tradingEngine.current.distanceToEvent.triggerOff.value = 1 + + announcementsModule.makeAnnoucements(triggerStage.triggerOff) + + logger.write(MODULE_NAME, '[INFO] checkTriggerOff -> Closing Strategy: ' + strategy.name) + return true // This Means that we have just met the conditions to trigger off. + } + } + } + } + } + return false // This Means that we have not met the conditions to trigger off. + } + + function checkTakePosition() { + /* Take Position Condition */ + if ( + tradingEngine.current.strategy.stageType.value === 'Trigger Stage' && + tradingEngine.current.strategy.status.value === 'Open' + ) { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.takePosition !== undefined) { + for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { + let situation = triggerStage.takePosition.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let value = false + if (conditions.get(condition.id) !== undefined) { + value = conditions.get(condition.id) + } + + if (value !== true) { passed = false } + } + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(triggerStage.takePosition.id) + tradingSystem.highlights.push(triggerStage.id) + + tradingStrategyModule.updateStageType('Open Stage') + tradingPositionModule.openPosition(situation.name) + + announcementsModule.makeAnnoucements(triggerStage.takePosition) + announcementsModule.makeAnnoucements(strategy.openStage) + + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.position === true) { + snapshotsModule.positionEntry() + } + } + } + + logger.write(MODULE_NAME, '[INFO] checkTakePosition -> Conditions at the Take Position Event were met.') + return true // This Means that we have just met the conditions to take position. + } + } + } + } + } + return false // This Means that we have not met the conditions to take position. + } + + function checkStopPhasesEvents() { + if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let phaseIndex + let phase + let stopLoss + let stage + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { + stage = openStage + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialStopLoss !== undefined) { + parentNode = openStage.initialDefinition + phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value + stopLoss = openStage.initialDefinition.initialStopLoss + phase = stopLoss.phases[phaseIndex] + } + } + } + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { + stage = manageStage + if (manageStage.managedStopLoss !== undefined) { + parentNode = manageStage + phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1 + stopLoss = manageStage.managedStopLoss + phase = stopLoss.phases[phaseIndex] + } + } + + if (parentNode !== undefined) { + if (phase === undefined) { return } // trying to jump to a phase that does not exists. + + /* Check the next Phase Event. */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let value = false + if (conditions.get(condition.id) !== undefined) { + value = conditions.get(condition.id) + } + if (value !== true) { passed = false } + } + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(nextPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(stopLoss.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage') + + if (tradingEngine.current.position.takeProfit.takeProfitPhase.value > 0) { + tradingStrategyModule.updateStageType('Manage Stage') + announcementsModule.makeAnnoucements(manageStage) + } + announcementsModule.makeAnnoucements(nextPhaseEvent) + return // only one event can pass at the time + } + } + } + + /* Check the Move to Phase Events. */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + let situation = moveToPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let value = false + if (conditions.get(condition.id) !== undefined) { + value = conditions.get(condition.id) + } + if (value !== true) { passed = false } + } + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(moveToPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(stopLoss.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < stopLoss.phases.length; q++) { + if (stopLoss.phases[q].id === moveToPhase.id) { + tradingPositionModule.updateStopLoss(q + 1, 'Manage Stage') + } + } + } else { + tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.']) + continue + } + + if (tradingEngine.current.position.takeProfit.takeProfitPhase.value > 0) { + tradingStrategyModule.updateStageType('Manage Stage') + announcementsModule.makeAnnoucements(manageStage) + } + announcementsModule.makeAnnoucements(moveToPhaseEvent) + return // only one event can pass at the time + } + } + } + } + } + } + } + + function calculateStopLoss() { + if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialStopLoss !== undefined) { + phase = openStage.initialDefinition.initialStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value] + } + } + } + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.managedStopLoss !== undefined) { + phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1] + } + } + + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = tradingEngine.current.position.stopLoss.value + tradingPositionModule.applyStopLossFormula(formulas, phase.formula.id) + + if (tradingEngine.current.position.stopLoss.value !== previousValue) { + announcementsModule.makeAnnoucements(phase) + } + } + } + } + } + + function checkTakeProfitPhaseEvents() { + if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let phaseIndex + let phase + let takeProfit + let stage + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { + stage = openStage + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialTakeProfit !== undefined) { + parentNode = openStage.initialDefinition + phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value + takeProfit = openStage.initialDefinition.initialTakeProfit + phase = takeProfit.phases[phaseIndex] + } + } + } + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { + stage = manageStage + if (manageStage.managedTakeProfit !== undefined) { + parentNode = manageStage + phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1 + takeProfit = manageStage.managedTakeProfit + phase = takeProfit.phases[phaseIndex] + } + } + + if (parentNode !== undefined) { + if (phase === undefined) { return } // trying to jump to a phase that does not exists. + + /* Check the next Phase Event. */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let value = false + if (conditions.get(condition.id) !== undefined) { + value = conditions.get(condition.id) + } + if (value !== true) { passed = false } + } + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(nextPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(takeProfit.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage') + + if (tradingEngine.current.position.stopLoss.stopLossPhase.value > 0) { + tradingStrategyModule.updateStageType('Manage Stage') + announcementsModule.makeAnnoucements(manageStage) + } + announcementsModule.makeAnnoucements(nextPhaseEvent) + return // only one event can pass at the time + } + } + } + + /* Check the Move to Phase Events. */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + let situation = moveToPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let value = false + if (conditions.get(condition.id) !== undefined) { + value = conditions.get(condition.id) + } + if (value !== true) { passed = false } + } + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(moveToPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(takeProfit.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < takeProfit.phases.length; q++) { + if (takeProfit.phases[q].id === moveToPhase.id) { + tradingPositionModule.updateTakeProfit(q + 1, 'Manage Stage') + } + } + } else { + tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.']) + continue + } + + if (tradingEngine.current.position.stopLoss.stopLossPhase.value > 0) { + tradingStrategyModule.updateStageType('Manage Stage') + announcementsModule.makeAnnoucements(manageStage) + } + announcementsModule.makeAnnoucements(moveToPhaseEvent) + return // only one event can pass at the time + } + } + } + } + } + } + } + + function calculateTakeProfit() { + if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialTakeProfit !== undefined) { + phase = openStage.initialDefinition.initialTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value] + } + } + } + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.managedTakeProfit !== undefined) { + phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1] + } + } + + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = tradingEngine.current.position.takeProfit.value + tradingPositionModule.applyTakeProfitFormula(formulas, phase.formula.id) + + if (tradingEngine.current.position.takeProfit.value !== previousValue) { + announcementsModule.makeAnnoucements(phase) + } + } + } + } + } + + function checkStopLossOrTakeProfitWasHit() { + { + if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + /* + Checking what happened since the last execution. We need to know if the Stop Loss + or our Take Profit were hit. + */ + + /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ + if ( + (bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value) || + (!bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value) + ) { + logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.') + + tradingPositionModule.preventStopLossDistortion() + tradingPositionModule.applySlippageToStopLoss() + tradingPositionModule.closePosition('Stop Loss') + tradingStrategyModule.updateStageType('Close Stage') + announcementsModule.makeAnnoucements(strategy.closeStage) + return true // This means that the STOP was hit. + } + + /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ + if ( + (bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value) || + (!bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value) + ) { + logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.') + + tradingPositionModule.preventTakeProfitDistortion() + tradingPositionModule.applySlippageToTakeProfit() + tradingPositionModule.closePosition('Take Profit') + tradingStrategyModule.updateStageType('Close Stage') + announcementsModule.makeAnnoucements(strategy.closeStage) + return true // This means that the Take Profit was hit. + } + } + return false // This means that the neither the STOP nor the Take Profit were hit. + } + } + + function getReadyToTakePosition() { + /* Updating Episode Counters */ + tradingEngine.episode.episodeCounters.positions.value++ + + /* Inicializing this counter */ + tradingEngine.current.distanceToEvent.takePosition.value = 1 + + /* Position size and rate */ + tradingPositionModule.updateSizeAndRate(getPositionSize(), getPositionRate()) + + /* We take what was calculated at the formula of position rate and apply the slippage. */ + tradingPositionModule.applySlippageToRate() + } + + function takePosition() { + tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + + let feePaid = 0 + + if (bot.sessionAndMarketBaseAssetsAreEqual) { + feePaid = tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 + + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value - feePaid + tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.current.position.size.value + } else { + feePaid = tradingEngine.current.position.size.value / tradingEngine.current.position.rate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 + + tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.current.position.size.value / tradingEngine.current.position.rate.value - feePaid + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.current.position.size.value + } + } + + function getReadyToClosePosition() { + /* Inicializing this counter */ + tradingEngine.current.distanceToEvent.closePosition.value = 1 + + /* Position size and rate */ + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + } + + function closePosition() { + /* We calculate the new balances after the position is closed. */ + let feePaid = 0 + if (bot.sessionAndMarketBaseAssetsAreEqual) { + feePaid = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 + tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value - feePaid + tradingEngine.current.balance.quotedAsset.value = 0 + } else { + feePaid = tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value - feePaid + tradingEngine.current.balance.baseAsset.value = 0 + } + + tradingPositionModule.updateStatistics() + + /* Recalculating Episode Counters */ + if (tradingEngine.current.position.positionStatistics.profitLoss.value > 0) { + tradingEngine.episode.episodeCounters.hits.value++ + } else { + tradingEngine.episode.episodeCounters.fails.value++ + } + + /* Recalculating Episode Statistics */ + if (bot.sessionAndMarketBaseAssetsAreEqual) { + tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionBaseAsset.config.initialBalance - 1 + tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.episodeCounters.positions.value + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days.value * 365 + } else { + tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionQuotedAsset.config.initialBalance - 1 + tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.episodeCounters.positions.value + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days.value * 365 + } + } + + function exitStrategyAfterPosition() { + if (tradingEngine.current.strategy.stageType.value !== 'Close Stage') { return } + + tradingStrategyModule.closeStrategy('Position Closed') + tradingEngine.current.distanceToEvent.triggerOff.value = 1 + + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.strategy === true) { + snapshotsModule.strategyExit() + } + } + } + + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.position === true) { + snapshotsModule.positionExit() + } + } + } + } + + function getPositionSize() { + let balance + if (bot.sessionAndMarketBaseAssetsAreEqual) { + balance = tradingEngine.current.balance.baseAsset.value + } else { + balance = tradingEngine.current.balance.quotedAsset.value + } + const DEFAULT_VALUE = balance + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + + if (strategy.openStage === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition.positionSize === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition.positionSize.formula === undefined) return DEFAULT_VALUE + + let value = formulas.get(strategy.openStage.initialDefinition.positionSize.formula.id) + if (value === undefined) return DEFAULT_VALUE + return value + } + + function getPositionRate() { + const DEFAULT_VALUE = tradingEngine.current.candle.close.value + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + + if (strategy.openStage === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition.positionRate === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition.positionRate.formula === undefined) return DEFAULT_VALUE + + let value = formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id) + if (value === undefined) return DEFAULT_VALUE + return value + } + + function isItInside(elementWithTimestamp, elementWithBeginEnd) { + /* + The function is to allow in Conditions and Formulas to easily know when an element with timestamp (like the ones of single files) are inside the time range + of an element with a time range, like a candle. + */ + if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) { + return true + } else { + return false + } + } +} + diff --git a/TaskServer/MultiPeriod.js b/TaskServer/MultiPeriod.js deleted file mode 100644 index 217e49077..000000000 --- a/TaskServer/MultiPeriod.js +++ /dev/null @@ -1,1182 +0,0 @@ -exports.newMultiPeriod = function newMultiPeriod(bot, logger, UTILITIES, USER_BOT_MODULE, USER_BOT_COMMONS) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const MODULE_NAME = "Multi Period"; - const GMT_SECONDS = ':00.000 GMT+0000'; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; - - thisObject = { - initialize: initialize, - finalize: finalize, - start: start - }; - - let utilities = UTILITIES.newCloudUtilities(logger); - - let statusDependencies; - let dataDependenciesModule; - let dataFiles = new Map(); - let multiPeriodDataFiles = new Map(); - - let botInstance; - - const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage = FILE_STORAGE.newFileStorage(logger); - - let processConfig; - - return thisObject; - - function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, exchangeAPI, callBackFunction) { - - try { - - logger.fileName = MODULE_NAME; - logger.initialize(); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - statusDependencies = pStatusDependencies; - dataDependenciesModule = pDataDependencies; - processConfig = pProcessConfig; - - let USER_BOT_MODULE = require("./TradingBot") - - botInstance = USER_BOT_MODULE.newTradingBot(bot, logger, UTILITIES, FILE_STORAGE); - botInstance.initialize(exchangeAPI, callBackFunction); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function finalize() { - dataFiles = undefined - multiPeriodDataFiles = undefined - statusDependencies = undefined - dataDependenciesModule = undefined - botInstance.finalize() - botInstance = undefined - fileStorage = undefined - processConfig = undefined - thisObject = undefined - } - - function start(callBackFunction) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } - - let currentTimeFrame - let currentTimeFrameLabel - - let market = bot.market; - let botNeverRan = true; - - /* Context Variables */ - - let contextVariables = { - lastFile: undefined, // Datetime of the last file files sucessfully produced by this process. - dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history. - dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process. - }; - - let previousDay; // Holds the date of the previous day relative to the processing date. - - let interExecutionMemoryArray = []; - - getContextVariables(); - - function getContextVariables() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Entering function."); } - - let thisReport; - let reportKey; - let statusReport; - - /* We are going to use the start date as beging of market date. */ - contextVariables.dateBeginOfMarket = bot.VALUES_TO_USE.timeRange.initialDatetime - - /* - Here we get the status report from the bot who knows which is the end of the market. - */ - - statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point") - - if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. - logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - if (statusReport.status === "Status Report is corrupt.") { - logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. "); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - thisReport = statusReport.file; - - if (thisReport.lastFile === undefined) { - logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport = " + JSON.stringify(thisReport)); - - let customOK = { - result: global.CUSTOM_OK_RESPONSE.result, - message: "Dependency not ready." - } - logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message); - callBackFunction(customOK); - return; - } - - contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf()); - - /* Validation that the data is not up-to-date. */ - - if (bot.multiPeriodProcessDatetime.valueOf() - ONE_DAY_IN_MILISECONDS > contextVariables.dateEndOfMarket.valueOf() && bot.SESSION.type !== "Backtesting Session") { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> " + "Head of the market is @ " + contextVariables.dateEndOfMarket); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> You need to UPDATE your datasets in order to run a " + bot.SESSION.type) } - - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - - } - - /* Finally we get our own Status Report. */ - - statusReport = statusDependencies.reportsByMainUtility.get("Self Reference") - - if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. - logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - if (statusReport.status === "Status Report is corrupt.") { - logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - - thisReport = statusReport.file; - - if (thisReport.lastFile !== undefined) { - - if (bot.hasTheBotJustStarted === true && bot.resumeExecution === false) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); } - startFromBegining(); - return; - } - - contextVariables.lastFile = new Date(thisReport.lastFile); - interExecutionMemoryArray = thisReport.interExecutionMemoryArray; - - processSingleFiles(); - return; - - } else { - - /* - In the case when there is no status report, we assume like the last processed file is the one on the date of Begining of Market. - */ - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); } - startFromBegining(); - return; - } - - function startFromBegining() { - - contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); } - - /* - The first time the process is running is the right time to create the data structure that is going to be shared across different executions. - This data structure has one object per each timeFrame. - */ - - interExecutionMemoryArray = []; - - for (let i = 0; i < global.dailyFilePeriods.length; i++) { - let emptyObject = {}; - interExecutionMemoryArray.push(emptyObject); - } - - processSingleFiles(); - - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack); - if (err.message === "Cannot read property 'file' of undefined") { - logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. "); - logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys)); - } - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function processSingleFiles() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> Entering function."); } - - try { - let dependencyIndex = 0; - dataFiles = new Map(); - - dependencyLoopBody(); - - function dependencyLoopBody() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> Entering function."); } - - let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; - let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; - - getFile(); - - function getFile() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> Entering function."); } - - if (datasetModule.node.code.codeName !== "Single-File") { - dependencyControlLoop(); - return - } - - if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.code.singularVariableName) !== true) { - dependencyControlLoop(); - return - } - - let fileName = "Data.json"; - let filePath = datasetModule.node.parentNode.code.codeName + '/' + datasetModule.node.code.codeName; - datasetModule.getTextFile(filePath, fileName, onFileReceived); - - function onFileReceived(err, text) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> text = " + text); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - if (err.message === 'File does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message); - callBackFunction(err); - } - return; - } - - let dataFile = JSON.parse(text); - dataFiles.set(dependency.id, dataFile); - - dependencyControlLoop(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> timeFramesLoop -> dependencyLoopBody -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function dependencyControlLoop() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyControlLoop -> Entering function."); } - - dependencyIndex++; - - if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - - dependencyLoopBody(); - - } else { - - let mapKey = "Single Files" - multiPeriodDataFiles.set(mapKey, dataFiles) - - processMarketFiles(); - - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyControlLoop -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function processMarketFiles() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> Entering function."); } - - try { - - let n; - - timeFramesLoop(); - - function timeFramesLoop() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoop -> Entering function."); } - - /* - We will iterate through all posible timeFrames. - */ - - n = 0 // loop Variable representing each possible period as defined at the timeFrames array. - - timeFramesLoopBody(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoop -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function timeFramesLoopBody() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> Entering function."); } - - const timeFrame = global.marketFilesPeriods[n][0]; - const timeFrameLabel = global.marketFilesPeriods[n][1]; - - let dependencyIndex = 0; - dataFiles = new Map(); - - if (bot.VALUES_TO_USE.timeFrame === timeFrameLabel) { - currentTimeFrame = global.marketFilesPeriods[n][0]; - currentTimeFrameLabel = global.marketFilesPeriods[n][1]; - } - dependencyLoopBody(); - - function dependencyLoopBody() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> Entering function."); } - - let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; - let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; - - getFile(); - - function getFile() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> Entering function."); } - - if (dependency.referenceParent.code.codeName !== "Multi-Period-Market") { - dependencyControlLoop(); - return - } - - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.code.singularVariableName) !== true) { - if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.code.pluralVariableName === 'candles')) { - dependencyControlLoop(); - return - } - } - - let fileName = "Data.json"; - let filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel; - datasetModule.getTextFile(filePath, fileName, onFileReceived); - - function onFileReceived(err, text) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> text = " + text); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - if (err.message === 'File does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message); - callBackFunction(err); - } - return; - } - - let dataFile = JSON.parse(text); - dataFiles.set(dependency.id, dataFile); - - dependencyControlLoop(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoop -> dependencyLoopBody -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function dependencyControlLoop() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> dependencyControlLoop -> Entering function."); } - - dependencyIndex++; - - if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - - dependencyLoopBody(); - - } else { - - let mapKey = global.marketFilesPeriods[n][1]; - multiPeriodDataFiles.set(mapKey, dataFiles) - - timeFramesControlLoop(); - - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> dependencyControlLoop -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function timeFramesControlLoop() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesControlLoop -> Entering function."); } - - n++; - - if (n < global.marketFilesPeriods.length) { - - timeFramesLoopBody(); - - } else { - - processDailyFiles(); - - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesControlLoop -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function processDailyFiles() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> Entering function."); } - - try { - - let n = 0; - - if (currentTimeFrame) { - callTheBot() - return - } - - bot.multiPeriodProcessDatetime = new Date(contextVariables.lastFile.valueOf() - ONE_DAY_IN_MILISECONDS); // Go back one day to start well when we advance time at the begining of the loop. - - advanceTime(); - - function advanceTime() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> Entering function."); } - - bot.multiPeriodProcessDatetime = new Date(bot.multiPeriodProcessDatetime.valueOf() + ONE_DAY_IN_MILISECONDS); - previousDay = new Date(bot.multiPeriodProcessDatetime.valueOf() - ONE_DAY_IN_MILISECONDS); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> bot.multiPeriodProcessDatetime = " + bot.multiPeriodProcessDatetime); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> previousDay = " + previousDay); } - - /* Validation that we are not going past the user defined end date. */ - - if (bot.multiPeriodProcessDatetime.valueOf() >= bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { - - const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); } - - bot.STOP_SESSION = true - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - - } - - /* Validation that we are not going past the head of the market. */ - - if (bot.multiPeriodProcessDatetime.valueOf() > contextVariables.dateEndOfMarket.valueOf() + ONE_DAY_IN_MILISECONDS - 1) { - - const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - - } - - checkStopTaskGracefully(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> advanceTime -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function checkStopTaskGracefully() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> checkStopTaskGracefully -> Entering function."); } - - /* Validation that we dont need to stop. */ - - if (global.STOP_TASK_GRACEFULLY === true) { - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - - } - - checkStopProcessing(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> checkStopTaskGracefully -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function checkStopProcessing() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> checkStopProcessing -> Entering function."); } - - /* Validation that we dont need to stop. */ - - if (bot.STOP_SESSION === true) { - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - - } - - timeFramesLoop(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> checkStopProcessing -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function timeFramesLoop() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoop -> Entering function."); } - - /* Telling the world we are alive and doing well */ - let processingDate = bot.multiPeriodProcessDatetime.getUTCFullYear() + '-' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCDate(), 2); - bot.processHeartBeat(processingDate) - - /* - - We will iterate through all posible timeFrames. - - */ - - n = 0 // loop Variable representing each possible period as defined at the timeFrames array. - - timeFramesLoopBody(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoop -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function timeFramesLoopBody() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> Entering function."); } - - const timeFrame = global.dailyFilePeriods[n][0]; - const timeFrameLabel = global.dailyFilePeriods[n][1]; - - if (processConfig.framework.validtimeFrames !== undefined) { - let validPeriod = false; - for (let i = 0; i < processConfig.framework.validtimeFrames.length; i++) { - let period = processConfig.framework.validtimeFrames[i]; - if (period === timeFrameLabel) { validPeriod = true } - } - if (validPeriod === false) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> Discarding period for not being listed as a valid period. -> timeFrameLabel = " + timeFrameLabel); } - timeFramesControlLoop(); - return; - } - } - - if (bot.VALUES_TO_USE.timeFrame === timeFrameLabel) { - currentTimeFrame = global.dailyFilePeriods[n][0]; - currentTimeFrameLabel = global.dailyFilePeriods[n][1]; - } - - let dependencyIndex = 0; - dataFiles = new Map(); - dependencyLoopBody(); - - function dependencyLoopBody() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> Entering function."); } - - let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; - let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; - - let previousFile; - let currentFile; - - if (bot.multiPeriodProcessDatetime.valueOf() > contextVariables.dateBeginOfMarket.valueOf()) { - getPreviousFile(); - } else { - previousFile = []; - getCurrentFile() - } - - function getPreviousFile() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> Entering function."); } - - if (dependency.referenceParent.code.codeName !== "Multi-Period-Daily") { - dependencyControlLoop(); - return - } - - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.code.singularVariableName) !== true) { - if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.code.pluralVariableName === 'candles')) { - dependencyControlLoop(); - return - } - } - - let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2); - let filePath - - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; - } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; - } - let fileName = "Data.json"; - - datasetModule.getTextFile(filePath, fileName, onFileReceived); - - function onFileReceived(err, text) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> text = " + text); } - - if ((err.message === "File does not exist." && botNeverRan === true) || err.code === 'The specified key does not exist.') { - - /* - Sometimes one of the dependencies of an indicator for some reasons are not calculated from the begining of the market. - When that happens we can not find those files. What we do in this situation is to move the time fordward until we can find - all the dependencies and the first run of the bot is successful. - - After that, we will not accept more missing files on any of the dependencies, and if any is missing we will abort the processing. - */ - logger.write(MODULE_NAME, "[WARN] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> Skipping day because file " + filePath + "/" + fileName + " was not found."); - - advanceTime(); - return; - } - - if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { - - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); - callBackFunction(err); - return; - } - - previousFile = JSON.parse(text); - - getCurrentFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getCurrentFile() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> Entering function."); } - - if (dependency.referenceParent.code.codeName !== "Multi-Period-Daily") { - dependencyControlLoop(); - return - } - - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.code.singularVariableName) !== true) { - if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.code.pluralVariableName === 'candles')) { - dependencyControlLoop(); - return - } - } - - let dateForPath = bot.multiPeriodProcessDatetime.getUTCFullYear() + '/' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCDate(), 2); - let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; - } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; - } - let fileName = "Data.json"; - - datasetModule.getTextFile(filePath, fileName, onFileReceived); - - function onFileReceived(err, text) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> text = " + text); } - - if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { - - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> Not Ok -> err = " + err.code); - callBackFunction(err); - return; - } - - currentFile = JSON.parse(text); - - let dataFile = previousFile.concat(currentFile); - - dataFiles.set(dependency.id, dataFile); - - dependencyControlLoop(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoop -> dependencyLoopBody -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function dependencyControlLoop() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> dependencyControlLoop -> Entering function."); } - - dependencyIndex++; - - if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - - dependencyLoopBody(); - - } else { - - let mapKey = global.dailyFilePeriods[n][1]; - multiPeriodDataFiles.set(mapKey, dataFiles) - - timeFramesControlLoop(); - - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> dependencyControlLoop -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function timeFramesControlLoop() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesControlLoop -> Entering function."); } - - n++; - - if (n < global.dailyFilePeriods.length) { - - timeFramesLoopBody(); - - } else { - - n = 0; - - if (currentTimeFrame !== undefined) { - callTheBot(); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesControlLoop -> Time Frame not Recognized. Can not continue." ); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesControlLoop -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function callTheBot() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> Entering function."); } - - botInstance.start( - multiPeriodDataFiles, - currentTimeFrame, - currentTimeFrameLabel, - bot.multiPeriodProcessDatetime, - interExecutionMemoryArray[n], - onBotFinished); - - function onBotFinished(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - callBackFunction(err); - return; - } - - botNeverRan = false; - - if (currentTimeFrame > global.dailyFilePeriods[0][0]) { - writeMarketStatusReport(onMarketStatusReport) - - } else { - writeDataRanges(currentTimeFrameLabel, onWritten); - } - - function onWritten(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> onWritten -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> callTheBot -> onBotFinished -> onWritten -> err = " + err.stack); - callBackFunction(err); - return; - } - - writeDailyStatusReport(bot.multiPeriodProcessDatetime, onDailyStatusReport); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> callTheBot -> onBotFinished -> onWritten -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - - function onDailyStatusReport() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> onDailyStatusReport -> Entering function."); } - - /* The next run we need the process to continue at the date it finished. */ - processConfig.framework.startDate.resumeExecution = true; - - advanceTime(); - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> callTheBot -> onBotFinished -> onDailyStatusReport -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function onMarketStatusReport() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> onMarketStatusReport -> Entering function."); } - - /* This is where we check if we need reached the user defined end datetime. */ - - const now = new Date() - if (now.valueOf() >= bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { - - const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> onMarketStatusReport -> " + logText); } - - bot.STOP_SESSION = true - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - - } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> callTheBot -> onBotFinished -> onMarketStatusReport -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> callTheBot -> onBotFinished -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> callTheBot -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeDataRanges(currentTimeFrameLabel, callBack) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRanges -> Entering function."); } - - let outputDatasetIndex = -1; - controlLoop(); - - function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.code.codeName; - writeDataRange(contextVariables.dateBeginOfMarket, bot.multiPeriodProcessDatetime, productCodeName, currentTimeFrameLabel, controlLoop); - } - - function controlLoop() { - - outputDatasetIndex++; - - if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { - productLoopBody(); - } else { - callBack(global.DEFAULT_OK_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRanges -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - - } - - function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel, callBack) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> Entering function."); } - - let dataRange = { - begin: pBegin.valueOf(), - end: pEnd.valueOf() + ONE_DAY_IN_MILISECONDS - }; - - let fileContent = JSON.stringify(dataRange); - - let fileName = '/Data.Range.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName; - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack); - callBack(err); - return; - } - - if (LOG_FILE_CONTENT === true) { - logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent); - } - - let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset - let event = { - dateRange: dataRange - } - - global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) - - callBack(global.DEFAULT_OK_RESPONSE); - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeDailyStatusReport(lastFileDate, callBack) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyStatusReport -> Entering function."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyStatusReport -> lastFileDate = " + lastFileDate); } - - try { - - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period" + "-" + "dataSet.V1"; - let thisReport = statusDependencies.statusReports.get(reportKey); - - thisReport.file.lastExecution = bot.currentDaytime; - thisReport.file.lastFile = lastFileDate; - thisReport.file.interExecutionMemoryArray = interExecutionMemoryArray; - thisReport.save(callBack); - - bot.hasTheBotJustStarted = false; - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyStatusReport -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeMarketStatusReport(callBack) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketStatusReport -> Entering function."); } - - try { - - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period" + "-" + "dataSet.V1"; - let thisReport = statusDependencies.statusReports.get(reportKey); - - thisReport.file.lastExecution = bot.processDatetime; - thisReport.save(callBack); - - logger.newInternalLoop(bot.codeName, bot.process, bot.multiPeriodProcessDatetime); - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketStatusReport -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } - - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function pad(str, max) { - str = str.toString(); - return str.length < max ? pad(" " + str, max) : str; - } -}; diff --git a/TaskServer/MultiPeriodDaily.js b/TaskServer/MultiPeriodDaily.js index 4e384081d..66e2f586d 100644 --- a/TaskServer/MultiPeriodDaily.js +++ b/TaskServer/MultiPeriodDaily.js @@ -4,7 +4,6 @@ const LOG_FILE_CONTENT = false; const MODULE_NAME = "Multi Period Daily"; const GMT_SECONDS = ':00.000 GMT+0000'; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; thisObject = { initialize: initialize, @@ -18,11 +17,11 @@ let dataDependenciesModule; let dataFiles = new Map; - let botInstance; + let indicatorOutputModule; let fileStorage = FILE_STORAGE.newFileStorage(logger); - let bootstrappingTheProcess = false + let bootstrappingTheProcess = false let processConfig; let beginingOfMarket @@ -42,13 +41,13 @@ dataDependenciesModule = pDataDependencies; processConfig = pProcessConfig; - let USER_BOT_MODULE = require("./IndicatorBot") + let INDICATOR_OUTPUT_MODULE = require("./IndicatorOutput") - botInstance = USER_BOT_MODULE.newIndicatorBot(bot, logger, UTILITIES, FILE_STORAGE); - botInstance.initialize(callBackFunction); + indicatorOutputModule = INDICATOR_OUTPUT_MODULE.newIndicatorOutput(bot, logger, UTILITIES, FILE_STORAGE); + indicatorOutputModule.initialize(callBackFunction); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -57,7 +56,7 @@ dataFiles = undefined statusDependencies = undefined dataDependenciesModule = undefined - botInstance = undefined + indicatorOutputModule = undefined fileStorage = undefined processConfig = undefined thisObject = undefined @@ -248,7 +247,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack); if (err.message === "Cannot read property 'file' of undefined") { logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. "); logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys)); @@ -280,7 +279,7 @@ bot.multiPeriodDailyProcessDatetime = new Date(bot.multiPeriodDailyProcessDatetime.valueOf() + ONE_DAY_IN_MILISECONDS); previousDay = new Date(bot.multiPeriodDailyProcessDatetime.valueOf() - ONE_DAY_IN_MILISECONDS); - + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> advanceTime -> bot.multiPeriodDailyProcessDatetime = " + bot.multiPeriodDailyProcessDatetime); } if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> advanceTime -> previousDay = " + previousDay); } @@ -300,7 +299,7 @@ let currentDateString = bot.multiPeriodDailyProcessDatetime.getUTCFullYear() + '-' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCDate(), 2); let currentDate = new Date(bot.multiPeriodDailyProcessDatetime) let percentage = global.getPercentage(fromDate, currentDate, lastDate) - bot.processHeartBeat(currentDateString, percentage) + bot.processHeartBeat(currentDateString, percentage) if (global.areEqualDates(currentDate, new Date()) === false) { logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage); @@ -309,7 +308,7 @@ checkStopTaskGracefully(); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> advanceTime -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> advanceTime -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -355,7 +354,7 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -395,9 +394,9 @@ let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; - if (dependency === undefined ) { + if (dependency === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> You need to add at least one Data Dependency to the process Multi Period Daily. Aborting process." ); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> You need to add at least one Data Dependency to the process Multi Period Daily. Aborting process."); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return; } @@ -422,10 +421,10 @@ let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2); let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } let fileName = "Data.json"; @@ -455,14 +454,14 @@ if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_RETRY_RESPONSE); return; } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); callBackFunction(err); return; } @@ -473,13 +472,13 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -492,12 +491,12 @@ let dateForPath = bot.multiPeriodDailyProcessDatetime.getUTCFullYear() + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCDate(), 2); let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } - let fileName = "Data.json"; + let fileName = "Data.json"; datasetModule.getTextFile(filePath, fileName, onFileReceived); @@ -543,19 +542,19 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> Catch Error -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> Catch Error -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -574,38 +573,38 @@ } else { - callTheBot(); + generateOutput(); } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function callTheBot() { + function generateOutput() { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> Entering function."); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> Entering function."); } const timeFrame = global.dailyFilePeriods[n][0]; const timeFrameLabel = global.dailyFilePeriods[n][1]; - botInstance.start( + indicatorOutputModule.start( dataFiles, timeFrame, timeFrameLabel, bot.multiPeriodDailyProcessDatetime, interExecutionMemoryArray[n], - onBotFinished); + onOutputGenerated); - function onBotFinished(err) { + function onOutputGenerated(err) { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> Entering function."); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> onOutputGenerated -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { @@ -617,19 +616,19 @@ periodsControlLoop(); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> onOutputGenerated -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -659,7 +658,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> controlLoop -> onWritten -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = " + err.stack); callBackFunction(err); return; } @@ -667,21 +666,21 @@ writeStatusReport(bot.multiPeriodDailyProcessDatetime, advanceTime); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -696,7 +695,7 @@ controlLoop(); function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.code.codeName; + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; writeDataRange(contextVariables.dateBeginOfMarket, bot.multiPeriodDailyProcessDatetime, productCodeName, controlLoop); } @@ -712,7 +711,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRanges -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRanges -> err = " + err.stack); callBack(global.DEFAULT_FAIL_RESPONSE); } @@ -741,7 +740,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack); callBack(err); return; } @@ -750,7 +749,7 @@ logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent); } - let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset let event = { dateRange: dataRange } @@ -761,7 +760,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> err = " + err.stack); callBack(global.DEFAULT_FAIL_RESPONSE); } } @@ -772,8 +771,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeStatusReport -> lastFileDate = " + lastFileDate); } try { - - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period-Daily" let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.currentDaytime; @@ -783,17 +781,16 @@ thisReport.save(callBack); bot.hasTheBotJustStarted = false; - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/TaskServer/MultiPeriodMarket.js b/TaskServer/MultiPeriodMarket.js index 3c8b10f07..a0b4d41ed 100644 --- a/TaskServer/MultiPeriodMarket.js +++ b/TaskServer/MultiPeriodMarket.js @@ -16,7 +16,7 @@ let dataDependenciesModule; let dataFiles = new Map(); - let botInstance; + let indicatorOutputModule; let processConfig; @@ -35,13 +35,13 @@ dataDependenciesModule = pDataDependencies; processConfig = pProcessConfig; - let USER_BOT_MODULE = require("./IndicatorBot") + let INDICATOR_OUTPUT_MODULE = require("./IndicatorOutput") - botInstance = USER_BOT_MODULE.newIndicatorBot(bot, logger, UTILITIES, FILE_STORAGE); - botInstance.initialize(callBackFunction); + indicatorOutputModule = INDICATOR_OUTPUT_MODULE.newIndicatorOutput(bot, logger, UTILITIES, FILE_STORAGE); + indicatorOutputModule.initialize(callBackFunction); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -50,7 +50,7 @@ dataFiles = undefined statusDependencies = undefined dataDependenciesModule = undefined - botInstance = undefined + indicatorOutputModule = undefined processConfig = undefined thisObject = undefined } @@ -100,7 +100,7 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -137,13 +137,13 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> Entering function."); } let dateForPath = bot.processDatetime.getUTCFullYear() + '/' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.processDatetime.getUTCDate(), 2); - let fileName = "Data.json"; + let fileName = "Data.json"; let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Market") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel; + if (dependency.referenceParent.config.codeName === "Multi-Period-Market") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } datasetModule.getTextFile(filePath, fileName, onFileReceived); @@ -156,7 +156,7 @@ if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); callBackFunction(err); return; } @@ -168,20 +168,20 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -200,38 +200,38 @@ } else { - callTheBot(); + generateOutput(); } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function callTheBot() { + function generateOutput() { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> Entering function."); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> Entering function."); } const timeFrame = global.marketFilesPeriods[n][0]; const timeFrameLabel = global.marketFilesPeriods[n][1]; - botInstance.start( + indicatorOutputModule.start( dataFiles, timeFrame, timeFrameLabel, undefined, undefined, - onBotFinished); + onOutputGenerated); - function onBotFinished(err) { + function onOutputGenerated(err) { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> Entering function."); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> onOutputGenerated -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { @@ -242,19 +242,19 @@ periodsControlLoop(); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> onOutputGenerated -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } - catch(err){ - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> err = "+ err.stack); + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> generateOutput -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -278,14 +278,14 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -296,7 +296,7 @@ try { - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period-Market" + "-" + "dataSet.V1"; + let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period-Market" let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.processDatetime; @@ -304,23 +304,23 @@ if (global.areEqualDates(bot.processDatetime, new Date()) === false) { logger.newInternalLoop(bot.codeName, bot.process, bot.processDatetime); - } - + } + /* Telling the world we are alive and doing well */ let currentDateString = bot.processDatetime.getUTCFullYear() + '-' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.processDatetime.getUTCDate(), 2); let currentDate = new Date(bot.processDatetime) let lastDate = new Date() - bot.processHeartBeat(currentDateString, global.getPercentage(currentDate, currentDate, lastDate)) + bot.processHeartBeat(currentDateString, global.getPercentage(currentDate, currentDate, lastDate)) } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/TaskServer/ProcessExecutionEvents.js b/TaskServer/ProcessExecutionEvents.js index 8731bf55a..b304be111 100644 --- a/TaskServer/ProcessExecutionEvents.js +++ b/TaskServer/ProcessExecutionEvents.js @@ -8,7 +8,7 @@ */ const MODULE_NAME = "Process Execution Events"; - logger.fileName = MODULE_NAME + logger.fileName = MODULE_NAME let bot = BOT; @@ -43,7 +43,7 @@ return } - let market = bot.market.baseAsset + '/' + bot.market.quotedAsset + let market = bot.market.baseAsset + '/' + bot.market.quotedAsset currentProcessKey = bot.processNode.referenceParent.name + "-" + bot.processNode.referenceParent.type + "-" + bot.processNode.referenceParent.id + "-" + bot.exchange + "-" + market if (bot.processNode.referenceParent.executionStartedEvent !== undefined) { @@ -52,7 +52,7 @@ processThisDependsOn = bot.processNode.referenceParent.executionStartedEvent.referenceParent.parentNode name = processThisDependsOn.name - if (processThisDependsOn.code.monthlyInstances === true) { + if (processThisDependsOn.config.monthlyInstances === true) { logger.fileName = MODULE_NAME + "." + name; } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " depends on " + name); } @@ -90,13 +90,13 @@ /* We found where the task that runs the process definition we are waiting for is located on the network. */ thisObject.networkNode = networkNode - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webSocketsPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } - eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.code.host, networkNode.code.webSocketsPort) + eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.config.host, networkNode.config.webSocketsPort) eventServerClient.initialize(onConnected) function onConnected() { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webSocketsPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } callBackFunction(global.DEFAULT_OK_RESPONSE); return @@ -119,7 +119,7 @@ return } callBackFunction(global.DEFAULT_OK_RESPONSE); - + } catch (err) { logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -151,7 +151,7 @@ let extraCallerId = '-' + Math.trunc(Math.random() * 10000) + '-' - let market = bot.market.baseAsset + '/' + bot.market.quotedAsset + let market = bot.market.baseAsset + '/' + bot.market.quotedAsset let key = processThisDependsOn.name + "-" + processThisDependsOn.type + "-" + processThisDependsOn.id + "-" + bot.exchange + "-" + market let callerId = bot.dataMine + "-" + bot.codeName + "-" + bot.process + extraCallerId @@ -163,7 +163,7 @@ eventServerClient.listenToEvent(key, 'Process Execution Finished', undefined, callerId, responseCallBack, eventsCallBack) - bot.processHeartBeat(undefined, undefined, "Waiting for " + thisObject.networkNode.name + "->" + processThisDependsOn.parentNode.parentNode.name + "->" + processThisDependsOn.parentNode.code.codeName + "->" + processThisDependsOn.code.codeName) + bot.processHeartBeat(undefined, undefined, "Waiting for " + thisObject.networkNode.name + "->" + processThisDependsOn.parentNode.parentNode.name + "->" + processThisDependsOn.parentNode.config.codeName + "->" + processThisDependsOn.config.codeName) function responseCallBack(message) { if (message.result !== global.DEFAULT_OK_RESPONSE.result) { @@ -192,9 +192,9 @@ global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Started') global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Started', event) - bot.processHeartBeat(undefined, undefined,"Running...") + bot.processHeartBeat(undefined, undefined, "Running...") - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> " + currentProcessKey + " Process Execution Started because " + key + " Finished."); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> " + currentProcessKey + " Process Execution Started because " + key + " Finished."); } } callBackFunction(message.event.err); @@ -208,7 +208,7 @@ global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Started') global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Started', event) - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> " + currentProcessKey + " Process Execution Started " ); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> " + currentProcessKey + " Process Execution Started "); } callBackFunction(global.DEFAULT_OK_RESPONSE); } @@ -232,7 +232,7 @@ global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Finished') global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Finished', event) - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] finish -> " + currentProcessKey + " Process Execution Finished " ); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] finish -> " + currentProcessKey + " Process Execution Finished "); } callBackFunction(global.DEFAULT_OK_RESPONSE); diff --git a/TaskServer/ProcessOutput.js b/TaskServer/ProcessOutput.js index 7bbdfff10..2130a7cf6 100644 --- a/TaskServer/ProcessOutput.js +++ b/TaskServer/ProcessOutput.js @@ -30,29 +30,29 @@ /* Some validations to verify that everything is in place. */ if (outputDataset.referenceParent !== undefined) { - if (outputDataset.referenceParent.code.codeName === undefined) { + if (outputDataset.referenceParent.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } if (outputDataset.referenceParent.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.code.codeName === undefined) { + if (outputDataset.referenceParent.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.code.codeName === undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } } else { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -76,10 +76,10 @@ /* All good, lets emit the event. */ - let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.code.codeName - let botName = outputDataset.referenceParent.parentNode.parentNode.code.codeName - let product = outputDataset.referenceParent.parentNode.code.codeName - let dataset = outputDataset.referenceParent.code.codeName + let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName + let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName + let product = outputDataset.referenceParent.parentNode.config.codeName + let dataset = outputDataset.referenceParent.config.codeName key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset let event = { diff --git a/TaskServer/Root.js b/TaskServer/Root.js index 3a0dec4cd..cecc40636 100644 --- a/TaskServer/Root.js +++ b/TaskServer/Root.js @@ -35,7 +35,6 @@ global.dailyFilePeriods = JSON.parse(global.dailyFilePeriods); - const ROOT_DIR = './'; const MODULE_NAME = "Root"; const WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START = 10000 // This avoid a race condition that could happen if one process finished before all the other even started. @@ -112,7 +111,7 @@ } catch (err) { - console.log(logDisplace + "Root : [ERROR] initialize -> err = " + err.stack); + console.log(logDisplace + "Root : [ERROR] initialize -> err = " + err.stack); return; } } @@ -132,28 +131,28 @@ /* Process Loops Declarations. */ - const INDICATOR_BOT_MAIN_LOOP_MODULE = require('./IndicatorBotProcessMainLoop'); - const SENSOR_BOT_MAIN_LOOP_MODULE = require('./SensorBotProcessMainLoop'); - const TRADING_ENGINE_MAIN_LOOP_MODULE = require('./TradingBotProcessMainLoop'); + const INDICATOR_BOT_MODULE = require('./IndicatorBot'); + const SENSOR_BOT = require('./SensorBot'); + const TRADING_BOT_MODULE = require('./TradingBot'); let botConfig; let processInstance = global.TASK_NODE.bot.processes[processIndex] /* Here we will check if we need to load the configuration and code of the bot from a file or we will take that from the UI. */ if ( - processInstance.referenceParent.code.framework !== undefined && - ( processInstance.referenceParent.code.framework.name === 'Multi-Period-Market' || - processInstance.referenceParent.code.framework.name === 'Multi-Period-Daily' || - processInstance.referenceParent.code.framework.name === 'Multi-Period') - ) { - botConfig = processInstance.referenceParent.parentNode.code + processInstance.referenceParent.config.framework !== undefined && + (processInstance.referenceParent.config.framework.name === 'Multi-Period-Market' || + processInstance.referenceParent.config.framework.name === 'Multi-Period-Daily' || + processInstance.referenceParent.config.framework.name === 'Low-Frequency-Trading-Process') + ) { + botConfig = processInstance.referenceParent.parentNode.config botConfig.definedByUI = true bootingBot(processIndex) return } else { getBotConfigFromFile(); return - } + } function getBotConfigFromFile() { @@ -162,7 +161,7 @@ const FILE_STORAGE = require('./FileStorage.js'); let fileStorage = FILE_STORAGE.newFileStorage(); - let filePath = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName + '/bots/' + global.TASK_NODE.bot.code.repo + '/this.bot.config.json'; + let filePath = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName + '/bots/' + global.TASK_NODE.bot.config.repo + '/this.bot.config.json'; fileStorage.getTextFile(filePath, onFileReceived); @@ -171,16 +170,16 @@ if (err.result !== global.DEFAULT_OK_RESPONSE.result) { console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> err = " + JSON.stringify(err)); console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> filePath = " + filePath); - console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> dataMine = " + global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName); + console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> dataMine = " + global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName); return; } try { botConfig = JSON.parse(text); - botConfig.repo = global.TASK_NODE.bot.code.repo; + botConfig.repo = global.TASK_NODE.bot.config.repo; bootingBot(processIndex); } catch (err) { - console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> err = " + JSON.stringify(err)); + console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> err = " + err.stack); return; } } @@ -195,7 +194,7 @@ try { - botConfig.process = global.TASK_NODE.bot.processes[processIndex].referenceParent.code.codeName + botConfig.process = global.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName botConfig.debug = {}; botConfig.processNode = global.TASK_NODE.bot.processes[processIndex] @@ -204,28 +203,21 @@ botConfig.DELETE_QUEUE_SIZE = 10 // This number represents how many log files can be at the queue at any point in time, which means how many logs are not still deleted. /* Simplifying the access to basic info */ - botConfig.dataMine = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName + botConfig.dataMine = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName botConfig.exchange = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode.name botConfig.exchangeNode = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode botConfig.market = { - baseAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent.code.codeName, - quotedAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent.code.codeName + baseAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent.config.codeName, + quotedAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent.config.codeName } - botConfig.uiStartDate = global.TASK_NODE.bot.code.startDate - botConfig.code = global.TASK_NODE.bot.code - - /* This stuff is still hardcoded and unresolved. */ - botConfig.version = { - "major": 1, - "minor": 0 - } - botConfig.dataSetVersion = "dataSet.V1" + botConfig.uiStartDate = global.TASK_NODE.bot.config.startDate + botConfig.config = global.TASK_NODE.bot.config /* Loop Counter */ - botConfig.loopCounter = 0; + botConfig.loopCounter = 0; /* File Path Root */ - botConfig.filePathRoot = botConfig.exchange + "/" + botConfig.market.baseAsset + "-" + botConfig.market.quotedAsset + "/" + botConfig.dataMine + "/" + botConfig.codeName ; + botConfig.filePathRoot = botConfig.exchange + "/" + botConfig.market.baseAsset + "-" + botConfig.market.quotedAsset + "/" + botConfig.dataMine + "/" + botConfig.codeName; /* Process Key */ botConfig.processKey = global.TASK_NODE.bot.processes[processIndex].name + '-' + global.TASK_NODE.bot.processes[processIndex].type + '-' + global.TASK_NODE.bot.processes[processIndex].id @@ -243,17 +235,17 @@ if (botConfig.processNode.marketReference.keyInstance.referenceParent !== undefined) { let key = botConfig.processNode.marketReference.keyInstance.referenceParent - process.env.KEY = key.code.codeName - process.env.SECRET = key.code.secret + process.env.KEY = key.config.codeName + process.env.SECRET = key.config.secret } } } } - let processConfig = global.TASK_NODE.bot.processes[processIndex].referenceParent.code + let processConfig = global.TASK_NODE.bot.processes[processIndex].referenceParent.config if (processConfig.framework !== undefined) { - if (processConfig.framework.name === "Multi-Period-Daily" || processConfig.framework.name === "Multi-Period-Market" || processConfig.framework.name === "Multi-Period") { + if (processConfig.framework.name === "Multi-Period-Daily" || processConfig.framework.name === "Multi-Period-Market" || processConfig.framework.name === "Low-Frequency-Trading-Process") { if (processConfig.framework.startDate !== undefined) { processConfig.framework.startDate.resumeExecution = true; if (processConfig.startMode.noTime !== undefined) { @@ -283,7 +275,7 @@ if (processConfig.startMode === undefined) { // Default botConfig.hasTheBotJustStarted = true; - + switch (botConfig.type) { case 'Sensor Bot': { runSensorBot(botConfig, processConfig); @@ -387,7 +379,7 @@ try { global.TOTAL_PROCESS_INSTANCES_CREATED++ - const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); + const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog'); let logger; logger = DEBUG_MODULE.newDebugLog(); @@ -396,14 +388,14 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runSensorBot -> Entering function."); } - let extractionBotMainLoop = SENSOR_BOT_MAIN_LOOP_MODULE.newSensorBotProcessMainLoop(pBotConfig, logger); - extractionBotMainLoop.initialize(pProcessConfig, onInitializeReady); + let sensorBot = SENSOR_BOT.newSensorBot(pBotConfig, logger); + sensorBot.initialize(pProcessConfig, onInitializeReady); function onInitializeReady(err) { if (err.result === global.DEFAULT_OK_RESPONSE.result) { - extractionBotMainLoop.run(whenRunFinishes); + sensorBot.run(whenRunFinishes); function whenRunFinishes(err) { @@ -412,14 +404,11 @@ let botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process; if (err.result === global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully."); logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId); logger.persist(); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> err = " + err.message); logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. "); logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bye."); @@ -452,7 +441,7 @@ try { global.TOTAL_PROCESS_INSTANCES_CREATED++ - const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); + const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog'); let logger; logger = DEBUG_MODULE.newDebugLog(); @@ -461,21 +450,21 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runIndicatorBot -> Entering function."); } - let indicatorBotMainLoop = INDICATOR_BOT_MAIN_LOOP_MODULE.newIndicatorBotProcessMainLoop(pBotConfig, logger); - indicatorBotMainLoop.initialize(pProcessConfig, onInitializeReady); + let indicatorBot = INDICATOR_BOT_MODULE.newIndicatorBot(pBotConfig, logger); + indicatorBot.initialize(pProcessConfig, onInitializeReady); function onInitializeReady(err) { if (err.result === global.DEFAULT_OK_RESPONSE.result) { - indicatorBotMainLoop.run(whenRunFinishes); + indicatorBot.run(whenRunFinishes); function whenRunFinishes(err) { pBotConfig.loopCounter = 0; let botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process; - + if (err.result === global.DEFAULT_OK_RESPONSE.result) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully."); @@ -515,7 +504,7 @@ global.TOTAL_PROCESS_INSTANCES_CREATED++ try { - const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); + const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog'); let logger; logger = DEBUG_MODULE.newDebugLog(); @@ -524,14 +513,14 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runTradingBot -> Entering function."); } - let tradingBotMainLoop = TRADING_ENGINE_MAIN_LOOP_MODULE.newTradingBotProcessMainLoop(pBotConfig, logger); - tradingBotMainLoop.initialize(pProcessConfig, onInitializeReady); + let tradingBot = TRADING_BOT_MODULE.newTradingBot(pBotConfig, logger); + tradingBot.initialize(pProcessConfig, onInitializeReady); function onInitializeReady(err) { if (err.result === global.DEFAULT_OK_RESPONSE.result) { - tradingBotMainLoop.run(whenRunFinishes); + tradingBot.run(whenRunFinishes); function whenRunFinishes(err) { diff --git a/TaskServer/SensorBotProcessMainLoop.js b/TaskServer/SensorBot.js similarity index 59% rename from TaskServer/SensorBotProcessMainLoop.js rename to TaskServer/SensorBot.js index 558d9ffa4..d8f8aaa32 100644 --- a/TaskServer/SensorBotProcessMainLoop.js +++ b/TaskServer/SensorBot.js @@ -1,8 +1,6 @@ -exports.newSensorBotProcessMainLoop = function newSensorBotProcessMainLoop(bot, parentLogger) { +exports.newSensorBot = function newSensorBot(bot, parentLogger) { - const ROOT_DIR = './'; - - const MODULE_NAME = "Sensor Process Main Loop"; + const MODULE_NAME = "Sensor Bot"; const FULL_LOG = true; let USER_BOT_MODULE; @@ -11,11 +9,10 @@ const FILE_STORAGE = require('./FileStorage.js'); let fileStorage = FILE_STORAGE.newFileStorage(parentLogger); - const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); + const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog'); let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble. let nextLoopTimeoutHandle; - let checkLoopHealthHandle; let thisObject = { initialize: initialize, @@ -27,12 +24,8 @@ return thisObject; function initialize(pProcessConfig, callBackFunction) { - /* This function is exactly the same in the 3 modules representing the 2 different bot types loops. */ - try { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - processConfig = pProcessConfig; if (bot.definedByUI === true) { @@ -47,7 +40,6 @@ fileStorage.getTextFile(filePath, onBotDownloaded); function onBotDownloaded(err, text) { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message); @@ -70,7 +62,6 @@ fileStorage.getTextFile(filePath, onCommonsDownloaded); function onCommonsDownloaded(err, text) { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.code || err.message); callBackFunction(global.DEFAULT_OK_RESPONSE); @@ -85,42 +76,37 @@ } } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } function run(callBackFunction) { - try { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] run -> Entering function."); } - - bot.enableCheckLoopHealth = true; - let fixedTimeLoopIntervalHandle; - if (bot.runAtFixedInterval === true) { + /* Errors sent to the UI */ + bot.processError = processError + /* Heartbeats sent to the UI */ + bot.processHeartBeat = processHeartBeat + + if (bot.runAtFixedInterval === true) { fixedTimeLoopIntervalHandle = setInterval(loop, bot.fixedInterval); loop(); // First run. - } else { - loop(); - } function loop() { - try { - processHeartBeat(undefined, undefined, "Running...") + processHeartBeat(undefined, undefined, "Running...") function pad(str, max) { str = str.toString(); return str.length < max ? pad(" " + str, max) : str; } /* For each loop we want to create a new log file. */ - if (logger !== undefined) { logger.finalize() } @@ -132,31 +118,24 @@ bot.loopCounter++; bot.loopStartTime = new Date().valueOf(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Entering function."); } - - /* Loop Heartbeat sent to the UI */ - bot.processHeartBeat = processHeartBeat - processHeartBeat() + /* We tell the UI that we are running. */ + processHeartBeat() /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ - - const UTILITIES = require(ROOT_DIR + 'CloudUtilities'); - const STATUS_REPORT = require(ROOT_DIR + 'StatusReport'); - const STATUS_DEPENDENCIES = require(ROOT_DIR + 'StatusDependencies'); - const PROCESS_EXECUTION_EVENTS = require(ROOT_DIR + 'ProcessExecutionEvents'); - const PROCESS_OUTPUT = require(ROOT_DIR + 'ProcessOutput'); + const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities'); + const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport'); + const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies'); + const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents'); + const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput'); /* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */ - bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution. - /* High level log entry */ - - console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30) - + " Main Loop # " + pad(Number(bot.loopCounter), 8) ) + /* High level log entry */ + console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30) + + " Main Loop # " + pad(Number(bot.loopCounter), 8)) /* We will prepare first the infraestructure needed for the bot to run. There are 4 modules we need to sucessfullly initialize first. */ - let processExecutionEvents let userBot; let statusDependencies; @@ -167,21 +146,12 @@ initializeProcessExecutionEvents(); function initializeProcessExecutionEvents() { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> Entering function."); } - processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) - processExecutionEvents.initialize(onInizialized); function onInizialized(err) { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Entering function."); } - switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well."); @@ -195,66 +165,41 @@ return; } case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + global.unexpectedError = err.message + processStopped() + return } } } catch (err) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + global.unexpectedError = err.message + processStopped() + return } } function startProcessExecutionEvents() { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> Entering function."); } - processExecutionEvents.start(onStarted); function onStarted(err) { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Entering function."); } - switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well."); @@ -274,68 +219,41 @@ return; } case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result); logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + global.unexpectedError = err.message + processStopped() + return } } function initializeStatusDependencies() { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> Entering function."); } - statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT); - statusDependencies.initialize(onInizialized); function onInizialized(err) { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Entering function."); } - switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well."); @@ -349,68 +267,41 @@ return; } case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } function initializeUserBot() { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> Entering function."); } - usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE, UTILITIES, FILE_STORAGE, STATUS_REPORT, exchangeAPI); - usertBot.initialize(statusDependencies, onInizialized); function onInizialized(err) { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> onInizialized -> Entering function."); } - switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> onInizialized > Execution finished well."); @@ -424,81 +315,51 @@ return; } case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } case global.CUSTOM_OK_RESPONSE.result: { switch (err.message) { default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } } } default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled err.result received. -> err.result = " + err.result); logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } function startUserBot() { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> Entering function."); } - usertBot.start(onFinished); function onFinished(err) { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> onFinished -> Entering function."); } - switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> onFinished > Execution finished well."); @@ -512,15 +373,10 @@ return; } case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } case global.CUSTOM_OK_RESPONSE.result: { @@ -539,66 +395,43 @@ } default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } } } default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Unhandled err.result received. -> err.result = " + err.result); logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } function finishProcessExecutionEvents() { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> Entering function."); } - processExecutionEvents.finish(onFinished); function onFinished(err) { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Entering function."); } - processExecutionEvents.finalize() - processExecutionEvents = undefined + processExecutionEvents = undefined switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { @@ -614,108 +447,63 @@ return; } case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; + global.unexpectedError = err.message + processStopped() + return } default: { logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + global.unexpectedError = err.message + processStopped() + return } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + global.unexpectedError = err.message + processStopped() + return } } function loopControl(nextWaitTime) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Entering function."); } if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); } /* We show we reached the end of the loop. */ /* Here we check if we must stop the loop gracefully. */ - shallWeStop(onStop, onContinue); function onStop() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Entering function."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); } - - if(global.WRITE_LOGS_TO_FILES === 'true'){ - logger.persist(); - } - - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; processStopped() - callBackFunction(global.DEFAULT_OK_RESPONSE); return; - } function onContinue() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onContinue -> Entering function."); } - /* Sensor bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */ - switch (nextWaitTime) { case 'Normal': { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.normalWaitTime / 1000) + " seconds."); } - if (processConfig.deadWaitTime > 0) { - checkLoopHealthHandle = setTimeout(checkLoopHealth, processConfig.deadWaitTime, bot.loopCounter); - } nextLoopTimeoutHandle = setTimeout(loop, processConfig.normalWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.normalWaitTime / 1000 + " seconds for next execution.") - if(global.WRITE_LOGS_TO_FILES === 'true'){ + processHeartBeat(undefined, undefined, "Waiting " + processConfig.normalWaitTime / 1000 + " seconds for next execution.") + if (global.WRITE_LOGS_TO_FILES === 'true') { logger.persist(); } } break; case 'Retry': { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); } - if (processConfig.deadWaitTime > 0) { - checkLoopHealthHandle = setTimeout(checkLoopHealth, processConfig.retryWaitTime * 5, bot.loopCounter); - } nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime); - processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.") + processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.") logger.persist(); } break; @@ -727,7 +515,7 @@ } else { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); } nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.") + processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.") logger.persist(); } } @@ -740,7 +528,7 @@ } else { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); } nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.") + processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.") logger.persist(); } } @@ -749,44 +537,9 @@ } } - function checkLoopHealth(pLastLoop) { - - if (bot.enableCheckLoopHealth === false || global.STOP_TASK_GRACEFULLY === true) { - - logger.write(MODULE_NAME, "[WARN] run -> loop -> checkLoopHealth -> bot.enableCheckLoopHealth = " + bot.enableCheckLoopHealth); - - return; - } // This gets disabled anytime the Main Loop is shut down by any condition. - - if (bot.loopCounter <= pLastLoop + 1) { // This means that the next loop started but also stopped executing abruptally. - - let now = new Date().valueOf(); - - if (now - bot.loopStartTime > processConfig.deadWaitTime) { - - logger.write(MODULE_NAME, "[ERROR] run -> loop -> checkLoopHealth -> Dead loop found -> pLastLoop = " + pLastLoop); - console.log((new Date().toISOString() + " [ERROR] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found dead. Resurrecting it now.")); - - logger.persist(); // We persist the logs of the failed execution. - clearTimeout(nextLoopTimeoutHandle); // We cancel the timeout in case the original loop was still running and schedulled to reexecute. - loop(); // We restart the loop so that the processing can continue. - - } else { - - console.log((new Date().toISOString() + " [WARN] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found delayed but still alive. No action taken.")); - - } - } - } - function shallWeStop(stopCallBack, continueCallBack) { - try { - /* IMPORTANT: This function is exactly the same on the 3 modules. */ - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> shallWeStop -> Entering function. "); } - if (!global.STOP_TASK_GRACEFULLY) { continueCallBack(); } else { @@ -794,23 +547,17 @@ } } catch (err) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + global.unexpectedError = err.message + processStopped() + return } } } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = "+ err.stack); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); + parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } @@ -824,17 +571,44 @@ global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) } + function processError(node, errorMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + errorMessage: errorMessage + } + } else { + event = { + errorMessage: errorMessage + } + } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Error', event) + } + function processStopped() { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') + if (global.unexpectedError !== undefined) { + processError(undefined, "An unexpected error caused the Process to stop.") + } else { + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') + } + logger.persist(); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); + if (global.unexpectedError !== undefined) { + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } else { + callBackFunction(global.DEFAULT_OK_RESPONSE) + } } } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] run -> err = "+ err.stack); + parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack); clearInterval(fixedTimeLoopIntervalHandle); clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/TaskServer/Session.js b/TaskServer/Session.js index 0045054a8..beedc4463 100644 --- a/TaskServer/Session.js +++ b/TaskServer/Session.js @@ -2,32 +2,29 @@ const MODULE_NAME = "Session" const FULL_LOG = true; - const ONE_YEAR_IN_MILISECONDS = 365 * 24 * 60 * 60 * 1000 let thisObject = { initialize: initialize } + const SOCIAL_BOTS_MODULE = require('./SocialBots.js') + let socialBotsModule = SOCIAL_BOTS_MODULE.newSocialBots(bot, parentLogger) + return thisObject; function initialize(pProcessConfig, callBackFunction) { try { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - let telegramAPI - /* Initialize this info so that everything is logged propeerly */ - bot.SESSION = { name: bot.processNode.session.name, id: bot.processNode.session.id } /* Set the folderName for early logging */ - if (bot.processNode.session.code.folderName === undefined) { + if (bot.processNode.session.config.folderName === undefined) { bot.SESSION.folderName = bot.SESSION.id } else { - bot.SESSION.folderName = bot.processNode.session.code.folderName + "-" + bot.SESSION.id + bot.SESSION.folderName = bot.processNode.session.config.folderName + "-" + bot.SESSION.id } /* Check if there is a session */ @@ -44,35 +41,35 @@ global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, runSession) global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, stopSession) + /* Errors sent to the UI */ + bot.sessionError = sessionError + + /* Heartbeats sent to the UI */ + bot.sessionHeartBeat = sessionHeartBeat + callBackFunction(global.DEFAULT_OK_RESPONSE) return function runSession(message) { - try { if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { return } // This happens when the UI is reloaded, the session was running and tries to run it again. /* We are going to run the Definition comming at the event. */ + bot.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema) bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem) + bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine) bot.SESSION = JSON.parse(message.event.session) bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter) + bot.RESUME = message.event.resume + bot.FIRST_EXECUTION = true + bot.SESSION.stop = stopSession // stop function - /* Set the folderName for logging, reports, context and data output */ - let code - if (bot.SESSION.code !== undefined) { - code = bot.SESSION.code - if (code.folderName === undefined) { - bot.SESSION.folderName = bot.SESSION.id - } else { - bot.SESSION.folderName = code.folderName + "-" + bot.SESSION.id - } - } + setUpAppSchema() + setUpSessionFolderName() - /* Extract values from different sources and consolidate them under one structure that is going to be used later on. */ - setValuesToUse(message) + if (checkParemeters() === false) { return } - /* Set up Social Bots */ - initializeSocialBots() + socialBotsModule.initialize() let allGood switch (bot.SESSION.type) { @@ -99,105 +96,253 @@ } else { bot.STOP_SESSION = true } + + socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is starting.") } catch (err) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> runSession -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function stopSession(message) { + function stopSession(commandOrigin) { try { + if (commandOrigin === undefined) { commandOrigin = ' from the User Interface.' } + socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin) + socialBotsModule.finalize() bot.STOP_SESSION = true - finalizeSocialBots() } catch (err) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> stopSession -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function checkDatetimes() { - if (bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf() < (new Date()).valueOf()) { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> checkDatetimes -> Overriding initialDatetime with present datetime because " + bot.VALUES_TO_USE.timeRange.initialDatetime + " is in the past."); } - bot.VALUES_TO_USE.timeRange.initialDatetime = new Date() + function setUpAppSchema() { + /* Setup the APP_SCHEMA_MAP based on the APP_SCHEMA_ARRAY */ + bot.APP_SCHEMA_MAP = new Map() + for (let i = 0; i < bot.APP_SCHEMA_ARRAY.length; i++) { + let nodeDefinition = bot.APP_SCHEMA_ARRAY[i] + let key = nodeDefinition.type + bot.APP_SCHEMA_MAP.set(key, nodeDefinition) } - if (bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf() < (new Date()).valueOf()) { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> checkDatetimes -> Overriding finalDatetime with present datetime plus one year because " + bot.VALUES_TO_USE.timeRange.finalDatetime + " is in the past."); } - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date() + ONE_YEAR_IN_MILISECONDS + } + + function setUpSessionFolderName() { + /* Set the folderName for logging, reports, context and data output */ + let config + if (bot.SESSION.config !== undefined) { + config = bot.SESSION.config + if (config.folderName === undefined) { + bot.SESSION.folderName = bot.SESSION.id + } else { + bot.SESSION.folderName = config.folderName + "-" + bot.SESSION.id + } } } + function checkParemeters() { + /* + Here we check all the Session Parameters received. If something critical is missing we abort returning false. If something + non critical is missing, we complete it with a default value. + */ + + if (bot.SESSION.parameters === undefined) { + let errorMessage = "Session Node with no Parameters." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION, errorMessage) + return false + } + + /* Time Range */ + if (bot.SESSION.parameters.timeRange === undefined) { // if the Time Range is missing we create a default one. + bot.SESSION.parameters.timeRange = { + name: 'Missing Time Range', + type: 'Time Range', + config: { + initialDatetime: (new Date()).valueOf(), + finalDatetime: (new Date()).valueOf() + } + } + } + if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined) { // if the initialDatetime is missing we create a default one. + bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date()).valueOf() + } else { + bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf() + } + if (bot.SESSION.parameters.timeRange.config.finalDatetime === undefined) { // if the finalDatetime is missing we create a default one. + bot.SESSION.parameters.timeRange.config.finalDatetime = (new Date()).valueOf() + } else { + bot.SESSION.parameters.timeRange.config.finalDatetime = (new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf() + } + /* Session Type Forced Values */ + switch (bot.SESSION.type) { + case 'Live Trading Session': { + if (bot.SESSION.parameters.timeRange.config.finalDatetime.valueOf() > (new Date()).valueOf()) { + bot.SESSION.parameters.timeRange.config.finalDatetime = new Date() + } + break + } + case 'Live Trading Session': { + bot.SESSION.parameters.timeRange.config.initialDatetime = new Date() + bot.SESSION.parameters.timeRange.config.finalDatetime = new Date((new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS) + break + } + case 'Fordward Testing Session': { + bot.SESSION.parameters.timeRange.config.initialDatetime = new Date() + bot.SESSION.parameters.timeRange.config.finalDatetime = new Date((new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS) + break + } + case 'Paper Trading Session': { + bot.SESSION.parameters.timeRange.config.initialDatetime = new Date() + bot.SESSION.parameters.timeRange.config.finalDatetime = new Date((new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS) + break + } + } + + /* Time Frame */ + if (bot.SESSION.parameters.timeFrame === undefined) { + let errorMessage = "Session Parameters Node with no Time Frame." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters, errorMessage) + return false + } + if (bot.SESSION.parameters.timeFrame.config.label === undefined) { + let errorMessage = "Session Parameters Node with no Time Frame Label configuration." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters.timeFrame, errorMessage) + return false + } + bot.SESSION.parameters.timeFrame.config.value = getTimeFrameFromLabel(bot.SESSION.parameters.timeFrame.config.label) + + /* Session Base Asset */ + if (bot.SESSION.parameters.sessionBaseAsset === undefined) { + let errorMessage = "Session Parameters Node with no Session Base Asset." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters, errorMessage) + return false + } + if (bot.SESSION.parameters.sessionBaseAsset.config.initialBalance === undefined) { + let errorMessage = "Session Parameters Session Base Asset with no initialBalance configuration." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters.sessionBaseAsset, errorMessage) + return false + } + if (bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance === undefined) { + bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance = 0 + } + if (bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance === undefined) { + bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance = Number.MAX_SAFE_INTEGER + } + + /* Session Quoted Asset */ + if (bot.SESSION.parameters.sessionQuotedAsset === undefined) { + let errorMessage = "Session Parameters Node with no Session Quoted Asset." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters, errorMessage) + return false + } + if (bot.SESSION.parameters.sessionQuotedAsset.config.initialBalance === undefined) { + let errorMessage = "Session Parameters Session Quoted Asset with no initialBalance configuration." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters.sessionQuotedAsset, errorMessage) + return false + } + if (bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance === undefined) { + bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance = 0 + } + if (bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance === undefined) { + bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance = Number.MAX_SAFE_INTEGER + } + + /* Slippage */ + if (bot.SESSION.parameters.slippage === undefined) { // if the Slippage is missing we create a default one. + bot.SESSION.parameters.timeRange = { + name: 'Missing Slippage', + type: 'Slippage', + config: { + positionRate: 0, + stopLoss: 0, + takeProfit: 0 + } + } + } + if (bot.SESSION.parameters.slippage.config.positionRate === undefined) { // if the positionRate is missing we create a default one. + bot.SESSION.parameters.slippage.config.positionRate = 0 + } + if (bot.SESSION.parameters.slippage.config.stopLoss === undefined) { // if the stopLoss is missing we create a default one. + bot.SESSION.parameters.slippage.config.stopLoss = 0 + } + if (bot.SESSION.parameters.slippage.config.takeProfit === undefined) { // if the takeProfit is missing we create a default one. + bot.SESSION.parameters.slippage.config.takeProfit = 0 + } + + /* Fee Structure */ + if (bot.SESSION.parameters.feeStructure === undefined) { // if the Fee Structure is missing we create a default one. + bot.SESSION.parameters.feeStructure = { + name: 'Missing Fee Structure', + type: 'Fee Structure', + config: { + maker: 0, + taker: 0 + } + } + } + if (bot.SESSION.parameters.feeStructure.config.maker === undefined) { // if the maker is missing we create a default one. + bot.SESSION.parameters.feeStructure.config.maker = 0 + } + if (bot.SESSION.parameters.feeStructure.config.taker === undefined) { // if the taker is missing we create a default one. + bot.SESSION.parameters.feeStructure.config.taker = 0 + } + + return true + } + function startBackTesting(message) { - bot.startMode = "Backtest" - if (bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf() > (new Date()).valueOf()) { - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date() - } - bot.resumeExecution = false; - bot.hasTheBotJustStarted = true - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) + bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) // Here we initialize the date of the process. return true } function startLiveTrading(message) { - - if (process.env.KEY === undefined || process.env.SECRET === undefined) { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> startLiveTrading -> Key name or Secret not provided, not possible to run the process in Live mode."); } - console.log("Key 'codeName' or 'secret' not provided. Plese check that and try again.") - return + if (process.env.KEY === undefined || process.env.SECRET === undefined) { // TODO Remove this from Process .env + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> Key 'codeName' or 'secret' not provided. Plese check that and try again."); } + return false } - bot.startMode = "Live" - checkDatetimes() - bot.resumeExecution = false; - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) - bot.hasTheBotJustStarted = true - pProcessConfig.liveWaitTime = getTimeFrameFromLabel(bot.VALUES_TO_USE.timeFrame) + bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) + pProcessConfig.liveWaitTime = sessionParameters.timeFrame.config.value return true } function startFordwardTesting(message) { - if (process.env.KEY === undefined || process.env.SECRET === undefined) { if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> startFordwardTesting -> Key name or Secret not provided, not possible to run the process in Forward Testing mode."); } console.log("Key 'codeName' or 'secret' not provided. Plese check that and try again.") - return + return false } - bot.startMode = "Live" - checkDatetimes() - bot.resumeExecution = false; - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) - bot.hasTheBotJustStarted = true + bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) /* Reduce the balance */ - let balancePercentage = 1 // This is the default value - if (bot.SESSION.code.balancePercentage !== undefined) { - balancePercentage = bot.SESSION.code.balancePercentage + if (bot.SESSION.config.balancePercentage !== undefined) { + balancePercentage = bot.SESSION.config.balancePercentage } - bot.VALUES_TO_USE.initialBalanceA = bot.VALUES_TO_USE.initialBalanceA * balancePercentage / 100 - bot.VALUES_TO_USE.initialBalanceB = bot.VALUES_TO_USE.initialBalanceB * balancePercentage / 100 + bot.SESSION.parameters.sessionBaseAsset.config.initialBalance = bot.SESSION.parameters.sessionBaseAsset.config.initialBalance * balancePercentage / 100 + bot.SESSION.parameters.sessionQuotedAsset.config.initialBalance = bot.SESSION.parameters.sessionQuotedAsset.config.initialBalance * balancePercentage / 100 - bot.VALUES_TO_USE.minimumBalanceA = bot.VALUES_TO_USE.minimumBalanceA * balancePercentage / 100 - bot.VALUES_TO_USE.minimumBalanceB = bot.VALUES_TO_USE.minimumBalanceB * balancePercentage / 100 + bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance = bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance * balancePercentage / 100 + bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance = bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance * balancePercentage / 100 - bot.VALUES_TO_USE.maximumBalanceA = bot.VALUES_TO_USE.maximumBalanceA * balancePercentage / 100 - bot.VALUES_TO_USE.maximumBalanceB = bot.VALUES_TO_USE.maximumBalanceB * balancePercentage / 100 + bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance = bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance * balancePercentage / 100 + bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance = bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance * balancePercentage / 100 - pProcessConfig.normalWaitTime = getTimeFrameFromLabel(bot.VALUES_TO_USE.timeFrame) + pProcessConfig.normalWaitTime = sessionParameters.timeFrame.config.value return true } function startPaperTrading(message) { - bot.startMode = "Paper" - - checkDatetimes() - bot.resumeExecution = false; - bot.hasTheBotJustStarted = true - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) - pProcessConfig.normalWaitTime = getTimeFrameFromLabel(bot.VALUES_TO_USE.timeFrame) + bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) + pProcessConfig.normalWaitTime = sessionParameters.timeFrame.config.value return true } @@ -222,290 +367,37 @@ } } - function setValuesToUse(message) { - /* - Base on the information received we will determined which values ultimatelly are going to be used, - and once we do, they will become constants across the multiple loops executions. - */ - - /* Set all default values */ - bot.VALUES_TO_USE = { - slippage: { - positionRate: 0, - stopLoss: 0, - takeProfit: 0 - }, - feeStructure: { - maker: 0, - taker: 0 - }, - timeRange: { - initialDatetime: new Date(), - finalDatetime: new Date() - } + function sessionHeartBeat(processingDate) { + let event = { + seconds: (new Date()).getSeconds(), + processingDate: processingDate } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Heartbeat', event) - /* Session Type Dependant Default Values */ - - switch (bot.SESSION.type) { - case 'Backtesting Session': { - bot.VALUES_TO_USE.timeRange.initialDatetime = new Date((new Date()).valueOf() - ONE_YEAR_IN_MILISECONDS) - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date() - break - } - case 'Live Trading Session': { - bot.VALUES_TO_USE.timeRange.initialDatetime = new Date() - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date((new Date()).valueOf() + ONE_YEAR_IN_MILISECONDS) - break - } - case 'Fordward Testing Session': { - bot.VALUES_TO_USE.timeRange.initialDatetime = new Date() - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date((new Date()).valueOf() + ONE_YEAR_IN_MILISECONDS) - break - } - case 'Paper Trading Session': { - bot.VALUES_TO_USE.timeRange.initialDatetime = new Date() - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date((new Date()).valueOf() + ONE_YEAR_IN_MILISECONDS) - break - } - } - - let tradingSystem = bot.TRADING_SYSTEM - - if (tradingSystem !== undefined) { - - /* Applying Session Level Parameters */ - if (bot.SESSION !== undefined) { - if (bot.SESSION.parameters !== undefined) { - - /* Base Asset and Initial Balances. */ - { - if (bot.SESSION.parameters.baseAsset !== undefined) { - if (bot.SESSION.parameters.baseAsset.referenceParent !== undefined) { - if (bot.SESSION.parameters.baseAsset.referenceParent.referenceParent !== undefined) { - - let code = bot.SESSION.parameters.baseAsset.referenceParent.referenceParent.code - - if (code.codeName !== undefined) { - bot.VALUES_TO_USE.baseAsset = code.codeName; - } - - code = bot.SESSION.parameters.baseAsset.code - - if (bot.VALUES_TO_USE.baseAsset === bot.market.baseAsset) { - if (code.initialBalance !== undefined) { - bot.VALUES_TO_USE.initialBalanceA = code.initialBalance; - bot.VALUES_TO_USE.initialBalanceB = 0 - } - if (code.minimumBalance !== undefined) { - bot.VALUES_TO_USE.minimumBalanceA = code.minimumBalance; - bot.VALUES_TO_USE.minimumBalanceB = 0 - } - if (code.maximumBalance !== undefined) { - bot.VALUES_TO_USE.maximumBalanceA = code.maximumBalance; - bot.VALUES_TO_USE.maximumBalanceB = 0 - } - } else { - if (code.initialBalance !== undefined) { - bot.VALUES_TO_USE.initialBalanceB = code.initialBalance; - bot.VALUES_TO_USE.initialBalanceA = 0 - } - if (code.minimumBalance !== undefined) { - bot.VALUES_TO_USE.minimumBalanceB = code.minimumBalance; - bot.VALUES_TO_USE.minimumBalanceA = 0 - } - if (code.maximumBalance !== undefined) { - bot.VALUES_TO_USE.maximumBalanceB = code.maximumBalance; - bot.VALUES_TO_USE.maximumBalanceA = 0 - } - } - - /* Solve when values are missing. */ - if (bot.VALUES_TO_USE.minimumBalanceA === undefined) { - bot.VALUES_TO_USE.minimumBalanceA = 0 - } - - if (bot.VALUES_TO_USE.minimumBalanceB === undefined) { - bot.VALUES_TO_USE.minimumBalanceB = 0 - } - - if (bot.VALUES_TO_USE.maximumBalanceA === undefined) { - bot.VALUES_TO_USE.maximumBalanceA = 10000000000000000 - } - - if (bot.VALUES_TO_USE.maximumBalanceB === undefined) { - bot.VALUES_TO_USE.maximumBalanceB = 10000000000000000 - } - } - } - } - } - - /* Quoted Asset. */ - { - if (bot.SESSION.parameters.quotedAsset !== undefined) { - if (bot.SESSION.parameters.quotedAsset.referenceParent !== undefined) { - if (bot.SESSION.parameters.quotedAsset.referenceParent.referenceParent !== undefined) { - - let code = bot.SESSION.parameters.quotedAsset.referenceParent.referenceParent.code - - if (code.codeName !== undefined) { - bot.VALUES_TO_USE.quotedAsset = code.codeName; - } - } - } - } - } - - /* Time Frame */ - if (bot.SESSION.parameters.timeFrame !== undefined) { - bot.VALUES_TO_USE.timeFrame = bot.SESSION.parameters.timeFrame.code.value - } - - /* Slippage */ - if (bot.SESSION.parameters.slippage !== undefined) { - if (bot.SESSION.parameters.slippage.code !== undefined) { - - let code = bot.SESSION.parameters.slippage.code - - if (code.positionRate !== undefined) { - bot.VALUES_TO_USE.slippage.positionRate = code.positionRate - } - if (code.stopLoss !== undefined) { - bot.VALUES_TO_USE.slippage.stopLoss = code.stopLoss - } - if (code.takeProfit !== undefined) { - bot.VALUES_TO_USE.slippage.takeProfit = code.takeProfit - } - } - } - - /* Fee Structure */ - if (bot.SESSION.parameters.feeStructure !== undefined) { - if (bot.SESSION.parameters.feeStructure.code !== undefined) { - - let code = bot.SESSION.parameters.feeStructure.code - - if (code.maker !== undefined) { - bot.VALUES_TO_USE.feeStructure.maker = code.maker - } - if (code.taker !== undefined) { - bot.VALUES_TO_USE.feeStructure.taker = code.taker - } - } - } - - /* Time Range */ - if (bot.SESSION.parameters.timeRange !== undefined) { - if (bot.SESSION.parameters.timeRange.code !== undefined) { - - let code = bot.SESSION.parameters.timeRange.code - if (code.initialDatetime !== undefined) { - if (isNaN(Date.parse(code.initialDatetime)) === true) { - parentLogger.write(MODULE_NAME, "[WARN] initialize -> runSession -> setValuesToUse -> Cannot use initialDatatime provided at Session Parameters because it is not a valid Date."); - } else { - bot.VALUES_TO_USE.timeRange.initialDatetime = new Date(code.initialDatetime) - } - } - if (code.finalDatetime !== undefined) { - if (isNaN(Date.parse(code.finalDatetime)) === true) { - parentLogger.write(MODULE_NAME, "[WARN] initialize -> runSession -> setValuesToUse -> Cannot use finalDatetime provided at Session Parameters because it is not a valid Date."); - } else { - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date(code.finalDatetime) - } - } - } - } - } - } + if (global.STOP_TASK_GRACEFULLY === true) { + bot.STOP_SESSION = true } } - function initializeSocialBots() { - - if (bot.SESSION.socialBots !== undefined) { - if (bot.SESSION.socialBots.bots !== undefined) { - for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { - let socialBot = bot.SESSION.socialBots.bots[i] - if (socialBot.type === "Telegram Bot") { - let code = socialBot.code - socialBot.botInstance = initializeTelegramBot(code.botToken, code.chatId) - } - } + function sessionError(node, errorMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + errorMessage: errorMessage } - - function announce(announcement) { - if (bot.SESSION.socialBots.bots !== undefined) { - for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { - let socialBot = bot.SESSION.socialBots.bots[i] - try { - let code = announcement.code - - if (socialBot.type === "Telegram Bot") { - if (announcement.formulaValue !== undefined) { - socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, announcement.formulaValue).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err)) - } else { - socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, code.text).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err)) - } - } - - } catch (err) { - parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> err = " + err.stack); - } - } - } - } - - bot.SESSION.socialBots.announce = announce - } - } - - function finalizeSocialBots() { - if (bot.SESSION.socialBots === undefined) { return } - if (bot.SESSION.socialBots.bots !== undefined) { - for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { - let socialBot = bot.SESSION.socialBots.bots[i] - if (socialBot.type === "Telegram Bot") { - let code = socialBot.code - socialBot.botInstance = finalizeTelegramBot(code.chatId) - } + } else { + event = { + errorMessage: errorMessage } } - } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Error', event) - function initializeTelegramBot(botToken, chatId) { - /* Telegram Bot Initialization */ - try { - const Telegraf = require('telegraf') - let telegramBot - telegramBot = new Telegraf(botToken) - telegramBot.start((ctx) => ctx.reply('Hi! I am the Telegram Bot that will tell you what happens with your trading session.')) - telegramBot.help((ctx) => ctx.reply('I can stop the session if you say STOP.')) - telegramBot.hears('STOP', (ctx) => stopSession()) - telegramBot.launch() - - const Telegram = require('telegraf/telegram') - - telegramAPI = new Telegram(botToken) - - const messge = bot.SESSION.type + " '" + bot.SESSION.name + "' was started with an initial balance of " + " " + bot.VALUES_TO_USE.initialBalanceA + " " + bot.VALUES_TO_USE.baseAsset + "." - telegramAPI.sendMessage(chatId, messge).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeTelegramBot -> Telegram API error -> err = " + err)) - - let botInstance = { - telegramBot: telegramBot, - telegramAPI: telegramAPI, - chatId: chatId - } - return botInstance - } catch (err) { - parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeTelegramBot -> err = " + err.stack); + if (global.STOP_TASK_GRACEFULLY === true) { + bot.STOP_SESSION = true } - - } - - function finalizeTelegramBot(chatId) { - const messge = bot.SESSION.type + " '" + bot.SESSION.name + "' was stopped." - telegramAPI.sendMessage(chatId, messge).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeTelegramBot -> Telegram API error -> err = " + err)) } } catch (err) { diff --git a/TaskServer/SocialBots.js b/TaskServer/SocialBots.js new file mode 100644 index 000000000..9ae190727 --- /dev/null +++ b/TaskServer/SocialBots.js @@ -0,0 +1,71 @@ +exports.newSocialBots = function newSocialBots(bot, logger) { + + const MODULE_NAME = 'Social Bots' + + let thisObject = { + sendMessage: sendMessage, + initialize: initialize, + finalize: finalize + } + + const TELEGRAM_BOT_MODULE = require('./TelegramBot.js') + + return thisObject + + function initialize() { + if (bot.SESSION.socialBots !== undefined) { + if (bot.SESSION.socialBots.bots !== undefined) { + for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { + let socialBot = bot.SESSION.socialBots.bots[i] + if (socialBot.type === "Telegram Bot") { + let config = socialBot.config + socialBot.botInstance = TELEGRAM_BOT_MODULE.newTelegramBot(bot, logger) + socialBot.botInstance.initialize(config.botToken, config.chatId) + } + } + } + + function announce(text) { + if (bot.SESSION.socialBots.bots !== undefined) { + for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { + let socialBot = bot.SESSION.socialBots.bots[i] + try { + if (socialBot.type === "Telegram Bot") { + socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, text).catch(err => logger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err)) + } + } catch (err) { + logger.write(MODULE_NAME, "[WARN] initialize -> announce -> err = " + err.stack); + } + } + } + } + + bot.SESSION.socialBots.announce = announce + } + } + + function finalize() { + if (bot.SESSION.socialBots === undefined) { return } + if (bot.SESSION.socialBots.bots !== undefined) { + for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { + let socialBot = bot.SESSION.socialBots.bots[i] + if (socialBot.type === "Telegram Bot") { + socialBot.botInstance.finalize() + } + } + } + } + + function sendMessage(message) { + if (bot.SESSION.socialBots !== undefined) { + if (bot.SESSION.socialBots.bots !== undefined) { + for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { + let socialBot = bot.SESSION.socialBots.bots[i] + if (socialBot.type === "Telegram Bot") { + socialBot.botInstance.sendMessage(message) + } + } + } + } + } +} \ No newline at end of file diff --git a/TaskServer/StatusDependencies.js b/TaskServer/StatusDependencies.js index 48b5659a9..21314c4c8 100644 --- a/TaskServer/StatusDependencies.js +++ b/TaskServer/StatusDependencies.js @@ -4,7 +4,7 @@ const LOG_FILE_CONTENT = false; const MODULE_NAME = "Status Dependencies"; - let bot = BOT + let bot = BOT let thisObject = { nodeArray: undefined, @@ -47,7 +47,7 @@ let statusDependency = { dependency: thisObject.nodeArray[i] } - + dependenciesToProcess.push(statusDependency) } @@ -124,7 +124,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> onLoad -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> onLoad -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -137,14 +137,14 @@ loadCount++; logger.write(MODULE_NAME, "[INFO] initialize -> addReport -> Total Added = " + loadCount); - let key = statusDependency.dependency.dataMine + "-" + statusDependency.dependency.bot + "-" + statusDependency.dependency.process + "-" + statusDependency.dependency.dataSetVersion; + let key = statusDependency.dependency.dataMine + "-" + statusDependency.dependency.bot + "-" + statusDependency.dependency.process thisObject.keys.push(key); thisObject.statusReports.set(key, statusReportModule); if (statusReportModule.mainUtility !== undefined) { thisObject.reportsByMainUtility.set(statusReportModule.mainUtility, statusReportModule) - } + } if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> addReport -> Report added to Map. -> key = " + key); } @@ -161,7 +161,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/TaskServer/StatusReport.js b/TaskServer/StatusReport.js index 0ae6bc288..ff40b4e74 100644 --- a/TaskServer/StatusReport.js +++ b/TaskServer/StatusReport.js @@ -23,7 +23,7 @@ status: undefined }; - let statusDependencyNode; + let statusDependencyNode; /* Utilities needed. */ @@ -32,7 +32,7 @@ /* Storage account to be used here. */ const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage + let fileStorage let sessionPath = '' @@ -48,67 +48,60 @@ /* Some very basic validations that we have all the information needed. */ if (statusDependencyNode.referenceParent === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Status Dependency without Reference Parent. Status Dependency = " + JSON.stringify(statusDependencyNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + validationFailed(statusDependencyNode, "Status Dependency without Reference Parent.") return } if (statusDependencyNode.referenceParent.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Status Report not attached to a Process Definition. Status Report = " + JSON.stringify(statusDependencyNode.referenceParent)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + validationFailed(statusDependencyNode.referenceParent, "Status Report not attached to a Process Definition.") return } - if (statusDependencyNode.referenceParent.parentNode.code.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Process Definition witn no codeName defined. Process Definition = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + if (statusDependencyNode.referenceParent.parentNode.config.codeName === undefined) { + validationFailed(statusDependencyNode.referenceParent.parentNode, "Process Definition witn no codeName defined.") return } if (statusDependencyNode.referenceParent.parentNode.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Process Definition not attached to a Bot. Process Definition = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + validationFailed(statusDependencyNode.referenceParent.parentNode, "Process Definition not attached to a Bot.") return } - if (statusDependencyNode.referenceParent.parentNode.parentNode.code.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Bot witn no codeName defined. Bot = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + if (statusDependencyNode.referenceParent.parentNode.parentNode.config.codeName === undefined) { + validationFailed(statusDependencyNode.referenceParent.parentNode.parentNode, "Bot with no codeName defined.") return } if (statusDependencyNode.referenceParent.parentNode.parentNode.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + validationFailed(statusDependencyNode.referenceParent.parentNode.parentNode, "Bot not attached to a Data Mine.") return } - if (statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + if (statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { + validationFailed(statusDependencyNode.referenceParent.parentNode.parentNode.parentNode, "Data Mine witn no codeName defined.") return } + function validationFailed(errorInNode, errorMessage) { + let nodeString = JSON.stringify(errorInNode) + logger.write(MODULE_NAME, "[ERROR] initialize -> " + errorMessage + ' -> nodeString = ' + nodeString) + bot.processError(errorInNode, errorMessage) + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + /* Simplifying the access to basic info */ - statusDependencyNode.bot = statusDependencyNode.referenceParent.parentNode.parentNode.code.codeName - statusDependencyNode.process = statusDependencyNode.referenceParent.parentNode.code.codeName + statusDependencyNode.bot = statusDependencyNode.referenceParent.parentNode.parentNode.config.codeName + statusDependencyNode.process = statusDependencyNode.referenceParent.parentNode.config.codeName statusDependencyNode.bottype = statusDependencyNode.referenceParent.parentNode.parentNode.type - statusDependencyNode.dataMine = statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.code.codeName + statusDependencyNode.dataMine = statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.config.codeName /* We retrieve the report main utility */ - if (statusDependencyNode.code !== undefined) { - if (statusDependencyNode.code.mainUtility !== undefined) { - thisObject.mainUtility = statusDependencyNode.code.mainUtility + if (statusDependencyNode.config !== undefined) { + if (statusDependencyNode.config.mainUtility !== undefined) { + thisObject.mainUtility = statusDependencyNode.config.mainUtility } } - /* This stuff is still hardcoded and unresolved. */ - statusDependencyNode.botVersion = { - "major": 1, - "minor": 0 - } - statusDependencyNode.dataSetVersion = "dataSet.V1" - if (bot.SESSION !== undefined && statusDependencyNode.bottype === "Trading Bot") { sessionPath = bot.SESSION.folderName + "/" } @@ -119,13 +112,13 @@ for (let i = 0; i < network.networkNodes.length; i++) { let networkNode = network.networkNodes[i] - + if (checkThisBranch(networkNode.dataMining) === true) { return } - if (checkThisBranch(networkNode.testingEnvironment) === true) {return} + if (checkThisBranch(networkNode.testingEnvironment) === true) { return } if (checkThisBranch(networkNode.productionEnvironment) === true) { return } function checkThisBranch(branch) { - if (branch === undefined) {return} + if (branch === undefined) { return } for (let j = 0; j < branch.exchangeTasks.length; j++) { let exchangeTasks = branch.exchangeTasks[j] for (let k = 0; k < exchangeTasks.taskManagers.length; k++) { @@ -156,9 +149,9 @@ /* We found where the task that runs the process definition this status report depends on and where it is located on the network. */ thisObject.networkNode = networkNode - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.code.host, networkNode.code.webPort); + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); callBackFunction(global.DEFAULT_OK_RESPONSE); return true } @@ -183,7 +176,7 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -204,16 +197,16 @@ let fileName = "Status.Report.json"; let filePath; - let ownerId = statusDependencyNode.dataMine + "-" + statusDependencyNode.bot + "-" + statusDependencyNode.botVersion.major + "-" + statusDependencyNode.botVersion.minor + "-" + statusDependencyNode.process + "-" + statusDependencyNode.dataSetVersion; - let botId = bot.dataMine + "-" + bot.codeName + "-" + bot.version.major + "-" + bot.version.minor + "-" + bot.process + "-" + bot.dataSetVersion; + let ownerId = statusDependencyNode.dataMine + "-" + statusDependencyNode.bot + "-" + statusDependencyNode.process + let botId = bot.dataMine + "-" + bot.codeName + "-" + bot.process if (ownerId !== botId) { let rootPath = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + statusDependencyNode.dataMine + "/" + statusDependencyNode.bot - filePath = rootPath + "/Reports/" + sessionPath + statusDependencyNode.process ; + filePath = rootPath + "/Reports/" + sessionPath + statusDependencyNode.process; } else { - filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process ; + filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process; } filePath += '/' + fileName @@ -236,10 +229,10 @@ function onFileReceived(err, text) { - if ( err.result === global.CUSTOM_FAIL_RESPONSE.result && (err.message === 'Folder does not exist.' || err.message === 'File does not exist.') + if (err.result === global.CUSTOM_FAIL_RESPONSE.result && (err.message === 'Folder does not exist.' || err.message === 'File does not exist.') || err.code === "The specified key does not exist.") { - logger.write(MODULE_NAME, "[INFO] load -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[INFO] load -> onFileReceived -> err = " + err.message); /* In this case we can assume that this is the first execution ever of this bot.*/ @@ -255,7 +248,7 @@ } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] load -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] load -> onFileReceived -> err = " + err.message); callBackFunction(err); return; } @@ -290,7 +283,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] load -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] load -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -301,8 +294,8 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] save -> Entering function."); } - let ownerId = statusDependencyNode.dataMine + "-" + statusDependencyNode.bot + "-" + statusDependencyNode.botVersion.major + "-" + statusDependencyNode.botVersion.minor + "-" + statusDependencyNode.process + "-" + statusDependencyNode.dataSetVersion; - let botId = bot.dataMine + "-" + bot.codeName + "-" + bot.version.major + "-" + bot.version.minor + "-" + bot.process + "-" + bot.dataSetVersion; + let ownerId = statusDependencyNode.dataMine + "-" + statusDependencyNode.bot + "-" + statusDependencyNode.process + let botId = bot.dataMine + "-" + bot.codeName + "-" + bot.process if (ownerId !== botId && statusDependencyNode.process !== "Context") { // Context is a special case where the report is created by the Context.js module itself. @@ -328,7 +321,7 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] save -> onFileCreated -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] save -> onFileCreated -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] save -> onFileCreated -> err = " + err.stack); callBackFunction(err); return; } @@ -348,7 +341,7 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] save -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] save -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/TaskServer/TelegramBot.js b/TaskServer/TelegramBot.js new file mode 100644 index 000000000..9e2383ed1 --- /dev/null +++ b/TaskServer/TelegramBot.js @@ -0,0 +1,51 @@ +exports.newTelegramBot = function newTelegramBot(bot, logger) { + + const MODULE_NAME = 'Telegram Bot' + + let thisObject = { + telegramBot: undefined, + telegramAPI: undefined, + chatId: undefined, + sendMessage: sendMessage, + initialize: initialize, + finalize: finalize + } + + return thisObject + + function initialize(botToken, chatId) { + /* Telegram Bot Initialization */ + try { + thisObject.chatId = chatId + + const Telegraf = require('telegraf') + + thisObject.telegramBot = new Telegraf(botToken) + thisObject.telegramBot.start((ctx) => ctx.reply('Hi! I am the Telegram Bot that will tell you what happens with your trading session.')) + thisObject.telegramBot.help((ctx) => ctx.reply('I can stop the session if you type STOP.')) + thisObject.telegramBot.hears('STOP', (ctx) => bot.SESSION.stop(' by STOP command at Telegram.')) + thisObject.telegramBot.launch() + + const Telegram = require('telegraf/telegram') + + thisObject.telegramAPI = new Telegram(botToken) + + const message = "Telegram bot is starting." + thisObject.telegramAPI.sendMessage(thisObject.chatId, message).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initializeTelegramBot -> Telegram API error -> err = " + err)) + + } catch (err) { + parentLogger.write(MODULE_NAME, "[WARN] initialize -> err = " + err.stack); + } + + } + + function finalize() { + const message = "Telegram bot is signing off." + thisObject.telegramAPI.sendMessage(thisObject.chatId, message).catch(err => parentLogger.write(MODULE_NAME, "[WARN] finalize -> Telegram API error -> err = " + err)) + } + + function sendMessage(message) { + thisObject.telegramAPI.sendMessage(thisObject.chatId, message).catch(err => parentLogger.write(MODULE_NAME, "[WARN] announce -> Telegram API error -> err = " + err)) + } + +} \ No newline at end of file diff --git a/TaskServer/TradingBot.js b/TaskServer/TradingBot.js index f9e713080..8b16f955e 100644 --- a/TaskServer/TradingBot.js +++ b/TaskServer/TradingBot.js @@ -1,1041 +1,748 @@ -exports.newTradingBot = function newTradingBot(bot, logger, UTILITIES, FILE_STORAGE) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; +exports.newTradingBot = function newTradingBot(bot, parentLogger) { const MODULE_NAME = "Trading Bot"; + const FULL_LOG = true; + + let USER_BOT_MODULE; + let USER_BOT_COMMONS; + + const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/TradingProcess.js'); + const FILE_STORAGE = require('./FileStorage.js'); + const SESSION = require(global.ROOT_DIR + 'Session'); + + let fileStorage = FILE_STORAGE.newFileStorage(parentLogger); + let session = SESSION.newSession(bot, parentLogger) + + const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog'); + let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble. + + let nextLoopTimeoutHandle; let thisObject = { initialize: initialize, - finalize: finalize, - start: start + run: run }; - let utilities = UTILITIES.newCloudUtilities(logger); - let fileStorage = FILE_STORAGE.newFileStorage(logger); - - const COMMONS = require('./Commons.js'); - let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE); - - let exchangeAPI; + let processConfig; return thisObject; - function finalize() { - thisObject = undefined - utilities = undefined - fileStorage = undefined - commons = undefined - } - - function initialize(pExchangeAPI, callBackFunction) { - + function initialize(pProcessConfig, callBackFunction) { + /* This function is exactly the same in the 3 modules representing the 2 different bot types loops. */ try { + processConfig = pProcessConfig; + if (bot.definedByUI === true) { + /* The code of the bot is defined at the UI. No need to load a file with the code. */ + session.initialize(processConfig, onSessionInitialized) - logger.fileName = MODULE_NAME; - logger.initialize(); + function onSessionInitialized(err) { + callBackFunction(err); + } + return + } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } + let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName + filePath += "/User.Bot.js" - exchangeAPI = pExchangeAPI; - callBackFunction(global.DEFAULT_OK_RESPONSE); + fileStorage.getTextFile(filePath, onBotDownloaded); + function onBotDownloaded(err, text) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message); + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + USER_BOT_MODULE = {} + USER_BOT_MODULE.newUserBot = eval(text); // Use this for production + + filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo; + filePath += "/Commons.js" + + fileStorage.getTextFile(filePath, onCommonsDownloaded); + + function onCommonsDownloaded(err, text) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.code || err.message); + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + + USER_BOT_COMMONS = {} + USER_BOT_COMMONS.newCommons = eval(text); // Use this for production + + session.initialize(onSessionInitialized) + + function onSessionInitialized(err) { + callBackFunction(err); + } + } + } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function start(multiPeriodDataFiles, timeFrame, timeFrameLabel, currentDay, interExecutionMemory, callBackFunction) { - + function run(callBackFunction) { try { + /* Some initial values*/ + let fixedTimeLoopIntervalHandle; + bot.STOP_SESSION = true; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } + /* Errors sent to the UI */ + bot.processError = processError - let chart = {} - let mainDependency = {} + /* Heartbeats sent to the UI */ + bot.processHeartBeat = processHeartBeat - /* The first phase here is about checking that we have everything we need at the definition level. */ - let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies - if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) {return} + loop(); - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets - if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + function loop() { + try { + processHeartBeat(undefined, undefined, "Running...") + function pad(str, max) { + str = str.toString(); + return str.length < max ? pad(" " + str, max) : str; + } - /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ - for (let j = 0; j < global.marketFilesPeriods.length; j++) { + /* For each loop we want to create a new log file. */ + if (logger !== undefined) { + logger.finalize() + } + logger = DEBUG_MODULE.newDebugLog(); + global.LOGGER_MAP.set(MODULE_NAME, logger) + logger.bot = bot; + logger.initialize(); - let timeFrameLabel = marketFilesPeriods[j][1] - let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) - let products = {} + bot.loopCounter++; + bot.loopStartTime = new Date().valueOf(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files for timeFrame = " + timeFrameLabel); } + let nextWaitTime; - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + /* We tell the UI that we are running. */ + processHeartBeat() - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - chart[propertyName] = products + /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ + const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities'); + const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport'); + const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies'); + const DATA_DEPENDENCIES = require(global.ROOT_DIR + 'DataDependencies'); + const DATA_SET = require(global.ROOT_DIR + 'DataSet'); + const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents'); + const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput'); + + /* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */ + bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution. + + /* High level log entry */ + console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30) + + " Main Loop # " + pad(Number(bot.loopCounter), 8) + " " + bot.processNode.session.type + " " + bot.processNode.session.name) + + /* Checking if we need to need to emit any event */ + if (bot.SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) { + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Running') + bot.SESSION_STATUS = 'Running' + } + + if (bot.SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) { + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped') + bot.SESSION_STATUS = 'Stopped' + } + + /* Checking if we should process this loop or not.*/ + if (bot.STOP_SESSION === true) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran."); } + + console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + + " Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran. "); + + nextWaitTime = 'Waiting for Session'; + loopControl(nextWaitTime); + return + } + + /* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */ + + let processExecutionEvents + let userBot; + let processFramework; + let statusDependencies; + let dataDependencies; + + initializeProcessExecutionEvents(); + + function initializeProcessExecutionEvents() { + try { + processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) + processExecutionEvents.initialize(onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well."); + startProcessExecutionEvents() + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function startProcessExecutionEvents() { + try { + processExecutionEvents.start(onStarted); + + function onStarted(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well."); + + if (global.STOP_TASK_GRACEFULLY === true) { + loopControl() + return + } + + initializeStatusDependencies(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function initializeStatusDependencies() { + try { + statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT); + statusDependencies.initialize(onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well."); + initializeDataDependencies(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + return; + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function initializeDataDependencies() { + try { + dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET); + dataDependencies.initialize(onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); } + switch (processConfig.framework.name) { + case 'Low-Frequency-Trading-Process': { + processFramework = TRADING_PROCESS_MODULE.newTradingProcess(bot, logger, UTILITIES, USER_BOT_MODULE, USER_BOT_COMMONS); + intitializeProcessFramework(); + break; + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported."); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + processConfig.framework.name); + global.unexpectedError = err.message + processStopped() + return + } + } + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function intitializeProcessFramework() { + try { + processFramework.initialize(processConfig, statusDependencies, dataDependencies, onInizialized); + + function onInizialized(err) { + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well."); + startProcessFramework(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + case global.CUSTOM_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function startProcessFramework() { + try { + processFramework.start(onFinished); + + function onFinished(err) { + try { + processFramework.finalize() + processFramework = undefined + dataDependencies.finalize() + dataDependencies = undefined + statusDependencies.finalize() + statusDependencies = undefined + + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well."); + nextWaitTime = 'Normal'; + finishProcessExecutionEvents() + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + case global.CUSTOM_OK_RESPONSE.result: { + + switch (err.message) { + case "Dependency does not exist.": { + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep."); + nextWaitTime = 'Sleep'; + loopControl(nextWaitTime); + return; + } + case "Dependency not ready.": { + logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep."); + nextWaitTime = 'Sleep'; + loopControl(nextWaitTime); + return; + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function finishProcessExecutionEvents() { + try { + processExecutionEvents.finish(onFinished); + + function onFinished(err) { + try { + processExecutionEvents.finalize() + processExecutionEvents = undefined + + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well."); + nextWaitTime = 'Normal'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process."); + global.unexpectedError = err.message + processStopped() + return + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + function loopControl(nextWaitTime) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); } + + /* We show we reached the end of the loop. */ + processHeartBeat() + + /* Here we check if we must stop the loop gracefully. */ + shallWeStop(onStop, onContinue); + + function onStop() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); } + + if (global.WRITE_LOGS_TO_FILES === 'true') { + logger.persist(); + } + + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped') + processStopped() + return; + } + + function onContinue() { + /* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */ + switch (nextWaitTime) { + case 'Waiting for Session': { + let waitTime = processConfig.sessionRunWaitTime + nextLoopTimeoutHandle = setTimeout(loop, waitTime); + let waitingTime = waitTime / 1000 / 60 + let label = 'minute/s' + if (waitingTime < 1) { + waitingTime = waitTime / 1000 + label = 'seconds' + } + processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran. ") + logger.persist(); + } + break + case 'Normal': { + let waitTime + switch (bot.SESSION.type) { + case 'Live Trading Session': { + waitTime = processConfig.liveTradingWaitTime + } + break + case 'Fordward Tessting Session': { + waitTime = processConfig.fordwardTestingWaitTime + } + break + case 'Paper Trading Session': { + waitTime = processConfig.paperTradingWaitTime + } + break + case 'Backtesting Session': { + waitTime = processConfig.backtestingWaitTime + } + break + } + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s."); } + nextLoopTimeoutHandle = setTimeout(loop, waitTime); + let waitingTime = waitTime / 1000 / 60 + let label = 'minute/s' + if (waitingTime < 1) { + waitingTime = waitTime / 1000 + label = 'seconds' + } + processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for next execution.") + logger.persist(); + } + break; + case 'Retry': { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); } + nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime); + processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.") + logger.persist(); + } + break; + case 'Sleep': { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); } + nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime); + processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.") + logger.persist(); + } + break; + case 'Coma': { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); } + nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime); + processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.") + logger.persist(); + } + break; + } + } + } + + function shallWeStop(stopCallBack, continueCallBack) { + try { + /* IMPORTANT: This function is exactly the same on the 3 modules. */ + if (!global.STOP_TASK_GRACEFULLY) { + continueCallBack(); + } else { + stopCallBack(); + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return + } + } + + } catch (err) { + parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack); + global.unexpectedError = err.message + processStopped() + return } } - for (let j = 0; j < global.dailyFilePeriods.length; j++) { - - let timeFrameLabel = global.dailyFilePeriods[j][1] - let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) - let products = {} - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files for timeFrame = " + timeFrameLabel); } - - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - chart[propertyName] = products + function processHeartBeat(processingDate, percentage, status) { + let event = { + seconds: (new Date()).getSeconds(), + processingDate: processingDate, + percentage: percentage, + status: status } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) } - /* Single Files */ - - let dataFiles = multiPeriodDataFiles.get('Single Files') - let products = {} - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files from Single Files."); } - - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - - let propertyName = 'atAnyTimeFrame'; - chart[propertyName] = products - } - - /* Simulation */ - - const TRADING_SIMULATION = require('./TradingSimulation.js'); - let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES); - - let market = bot.market; - - const SIMULATED_RECORDS_FOLDER_NAME = "Trading-Simulation"; - 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; - - let recordsArray - let conditionsArray - let strategiesArray - let tradesArray - - let snapshotHeaders - let triggerOnSnapshot - let takePositionSnapshot - - let tradingSystem = {}; - - tradingSimulation.runSimulation( - chart, - dataDependencies, - timeFrame, - timeFrameLabel, - currentDay, - interExecutionMemory, - exchangeAPI, - writeFiles, - callBackFunction) - - function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray, pSnapshotHeaders, pTriggerOnSnapshot, pTakePositionSnapshot) { - - tradingSystem = pTradingSystem - recordsArray = pRecordsArray - conditionsArray = pConditionsArray - strategiesArray = pStrategiesArray - tradesArray = pTradesArray - - snapshotHeaders = pSnapshotHeaders - triggerOnSnapshot = pTriggerOnSnapshot - takePositionSnapshot = pTakePositionSnapshot - - if (timeFrame > global.dailyFilePeriods[0][0]) { - writeMarketFiles() + function processError(node, errorMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + errorMessage: errorMessage + } } else { - writeDailyFiles() + event = { + errorMessage: errorMessage + } } - + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Error', event) } - function writeMarketFiles() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> Entering function."); } - - writeRecordsFile(); - - function writeRecordsFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < recordsArray.length; i++) { - - let record = recordsArray[i]; - - 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 = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeConditionsFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + function processStopped() { + if (global.unexpectedError !== undefined) { + processError(undefined, "An unexpected error caused the Process to stop.") + } else { + global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') } - - function writeConditionsFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < conditionsArray.length; i++) { - - let record = conditionsArray[i]; - - let conditions = ""; - let conditionsSeparator = ""; - - for (let j = 0; j < record.length - 3; j++) { - conditions = conditions + conditionsSeparator + record[j]; - if (conditionsSeparator === "") { conditionsSeparator = ","; } - } - - conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']'; // The last item contains an Array of condition values. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']'; // The last item contains an Array of formulaErrors. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']'; // The last item contains an Array of formulaValues. - - fileContent = fileContent + separator + '[' + conditions + ']'; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - fileContent = "[" + JSON.stringify(tradingSystem) + "," + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeStrategiesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeStrategiesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < strategiesArray.length; i++) { - let record = strategiesArray[i]; - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.number + "," + - record.beginRate + "," + - record.endRate + "," + - '"' + record.triggerOnSituation + '"' + "," + - '"' + record.name + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeTradesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeTradesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < tradesArray.length; i++) { - - let record = tradesArray[i]; - if (record.stopRate === undefined) { record.stopRate = 0 } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.lastTradeROI + "," + - record.beginRate + "," + - record.endRate + "," + - record.exitType + "," + - '"' + record.takePositionSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeSnapshotFiles(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeSnapshotFiles() { - - writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish) - - function onFinish() { - writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction) - } - - } - - function writeSnapshotFile(snapshotArray, pFileName, callBack) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeSnapshotFile -> Entering function."); } - - let fileRecordCounter = 0; - - let fileContent = ""; - let separator = "\r\n"; - - 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 + ""; - - 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); - - function onFileCreated(err) { - - try { - - 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 -> 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; - - } - - callBack(global.DEFAULT_OK_RESPONSE); - - } - catch (err) { - 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 -> 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 { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - 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 = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeConditionsFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeConditionsFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < conditionsArray.length; i++) { - - let record = conditionsArray[i]; - - /* Will only add to the file the records of the current day */ - - if (record.begin < currentDay.valueOf()) { continue; } - - let conditions = ""; - let conditionsSeparator = ""; - - /* Will only add to the file the records of the current day */ - - if (record.begin < currentDay.valueOf()) { continue; } - - for (let j = 0; j < record.length - 3; j++) { - conditions = conditions + conditionsSeparator + record[j]; - if (conditionsSeparator === "") { conditionsSeparator = ","; } - } - - conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']'; // The last item contains an Array of condition values. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']'; // The last item contains an Array of formulaErrors. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']'; // The last item contains an Array of formulaValues. - - fileContent = fileContent + separator + '[' + conditions + ']'; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - fileContent = "[" + JSON.stringify(tradingSystem) + "," + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeStrategiesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeStrategiesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < strategiesArray.length; i++) { - let record = strategiesArray[i]; - - /* Will only add to the file the records of the current day. In this case since objects can span more than one day, we add all of the objects that ends - at the current date. */ - - if (record.end < currentDay.valueOf()) { continue; } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.number + "," + - record.beginRate + "," + - record.endRate + "," + - '"' + record.triggerOnSituation + '"' + "," + - '"' + record.name + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeTradesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeTradesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < tradesArray.length; i++) { - - let record = tradesArray[i]; - - /* Will only add to the file the records of the current day. In this case since objects can span more than one day, we add all of the objects that ends - at the current date. */ - - if (record.end < currentDay.valueOf()) { continue; } - if (record.stopRate === undefined) { record.stopRate = 0 } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.lastTradeROI + "," + - record.beginRate + "," + - record.endRate + "," + - record.exitType + "," + - '"' + record.takePositionSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeSnapshotFiles() - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeSnapshotFiles() { - - writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish) - - function onFinish() { - writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction) - } - - } - - function writeSnapshotFile(snapshotArray, pFileName, callBack) { - if (bot.startMode !== 'Backtest') { - callBack(global.DEFAULT_OK_RESPONSE); - return; - } - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeSnapshotFile -> Entering function."); } - - let fileRecordCounter = 0; - - let fileContent = ""; - let separator = "\r\n"; - - 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 + ""; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = pFileName + '.csv'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SNAPSHOTS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - callBack(global.DEFAULT_OK_RESPONSE); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + sessionStopped() + logger.persist(); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); + if (global.unexpectedError !== undefined) { + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } else { + callBackFunction(global.DEFAULT_OK_RESPONSE) } } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); + + function sessionStopped() { + if (bot.SESSION_STATUS === 'Running') { + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped') + bot.SESSION_STATUS = 'Stopped' + } + } + + } catch (err) { + parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } }; - diff --git a/TaskServer/TradingBotProcessMainLoop.js b/TaskServer/TradingBotProcessMainLoop.js deleted file mode 100644 index 579de4f33..000000000 --- a/TaskServer/TradingBotProcessMainLoop.js +++ /dev/null @@ -1,1071 +0,0 @@ -exports.newTradingBotProcessMainLoop = function newTradingBotProcessMainLoop(bot, parentLogger) { - - const ROOT_DIR = './'; - - const MODULE_NAME = "Trading Process Main Loop"; - const FULL_LOG = true; - - let USER_BOT_MODULE; - let USER_BOT_COMMONS; - - const MULTI_PERIOD_MARKET = require(ROOT_DIR + 'MultiPeriodMarket'); - const MULTI_PERIOD_DAILY = require(ROOT_DIR + 'MultiPeriodDaily'); - const MULTI_PERIOD = require(ROOT_DIR + 'MultiPeriod'); - const FILE_STORAGE = require('./FileStorage.js'); - const SESSION = require(ROOT_DIR + 'Session'); - - let fileStorage = FILE_STORAGE.newFileStorage(parentLogger); - let session = SESSION.newSession(bot, parentLogger) - - const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); - let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble. - - let nextLoopTimeoutHandle; - let checkLoopHealthHandle; - - let thisObject = { - initialize: initialize, - run: run - }; - - let processConfig; - - return thisObject; - - function initialize(pProcessConfig, callBackFunction) { - - /* This function is exactly the same in the 3 modules representing the 2 different bot types loops. */ - - try { - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - processConfig = pProcessConfig; - - if (bot.definedByUI === true) { - /* The code of the bot is defined at the UI. No need to load a file with the code. */ - session.initialize(processConfig, onSessionInitialized) - - function onSessionInitialized(err) { - callBackFunction(err); - } - return - } - - bot.resumeExecution = processConfig.framework.startDate.resumeExecution // We are inherating this from root, but from here we need it at bot - - let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName - filePath += "/User.Bot.js" - - fileStorage.getTextFile(filePath, onBotDownloaded); - - function onBotDownloaded(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message); - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - - USER_BOT_MODULE = {} - USER_BOT_MODULE.newUserBot = eval(text); // Use this for production - - filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo; - filePath += "/Commons.js" - - fileStorage.getTextFile(filePath, onCommonsDownloaded); - - function onCommonsDownloaded(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.code || err.message); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - - USER_BOT_COMMONS = {} - USER_BOT_COMMONS.newCommons = eval(text); // Use this for production - - session.initialize(onSessionInitialized) - - function onSessionInitialized(err) { - callBackFunction(err); - } - } - } - } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function run(callBackFunction) { - - try { - - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] run -> Entering function."); } - - /* Some initial values*/ - bot.enableCheckLoopHealth = true; - let fixedTimeLoopIntervalHandle; - bot.STOP_SESSION = true; - - loop(); - - function loop() { - - try { - - processHeartBeat(undefined, undefined, "Running...") - function pad(str, max) { - str = str.toString(); - return str.length < max ? pad(" " + str, max) : str; - } - - /* For each loop we want to create a new log file. */ - if (logger !== undefined) { - logger.finalize() - } - logger = DEBUG_MODULE.newDebugLog(); - global.LOGGER_MAP.set(MODULE_NAME, logger) - logger.bot = bot; - logger.initialize(); - - bot.loopCounter++; - bot.loopStartTime = new Date().valueOf(); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Entering function."); } - - let nextWaitTime; - - /* Heartbeats sent to the UI */ - bot.sessionHeartBeat = sessionHeartBeat - bot.processHeartBeat = processHeartBeat - processHeartBeat() - - /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ - - const UTILITIES = require(ROOT_DIR + 'CloudUtilities'); - const EXCHANGE_API = require(ROOT_DIR + 'ExchangeAPI'); - const STATUS_REPORT = require(ROOT_DIR + 'StatusReport'); - const STATUS_DEPENDENCIES = require(ROOT_DIR + 'StatusDependencies'); - const DATA_DEPENDENCIES = require(ROOT_DIR + 'DataDependencies'); - const DATA_SET = require(ROOT_DIR + 'DataSet'); - const PROCESS_EXECUTION_EVENTS = require(ROOT_DIR + 'ProcessExecutionEvents'); - const PROCESS_OUTPUT = require(ROOT_DIR + 'ProcessOutput'); - - /* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */ - - bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution. - - /* High level log entry */ - - console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30) - + " Main Loop # " + pad(Number(bot.loopCounter), 8) + " " + bot.processNode.session.type + " " + bot.processNode.session.name) - - /* Checking if we need to need to emit any event */ - - if (bot.SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Running') - bot.SESSION_STATUS = 'Running' - } - - if (bot.SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped') - bot.SESSION_STATUS = 'Stopped' - } - - /* Checking if we should process this loop or not.*/ - if (bot.STOP_SESSION === true) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran."); } - - console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) - + " Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran. "); - - nextWaitTime = 'Normal'; - loopControl(nextWaitTime); - return - } - - /* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */ - - let processExecutionEvents - let userBot; - let processFramework; - let statusDependencies; - let dataDependencies; - - initializeProcessExecutionEvents(); - - function initializeProcessExecutionEvents() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> Entering function."); } - - processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) - - processExecutionEvents.initialize(onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well."); - startProcessExecutionEvents() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function startProcessExecutionEvents() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> Entering function."); } - - processExecutionEvents.start(onStarted); - - function onStarted(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well."); - - if (global.STOP_TASK_GRACEFULLY === true) { - loopControl() - return - } - - initializeStatusDependencies(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function initializeStatusDependencies() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> Entering function."); } - - statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT); - - statusDependencies.initialize(onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well."); - initializeDataDependencies(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - sessionStopped() - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function initializeDataDependencies() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> Entering function."); } - - dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET); - - dataDependencies.initialize(onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); } - initializeExchangeAPI(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - sessionStopped() - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function initializeExchangeAPI() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeExchangeAPI -> Entering function."); } - - exchangeAPI = EXCHANGE_API.newExchangeAPI(logger, bot); - - exchangeAPI.initialize(onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeContext -> onInizialized -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeExchangeAPI -> onInizialized -> Execution finished well."); } - - switch (processConfig.framework.name) { - case 'Multi-Period': { - processFramework = MULTI_PERIOD.newMultiPeriod(bot, logger, UTILITIES, USER_BOT_MODULE, USER_BOT_COMMONS); - intitializeProcessFramework(); - break; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Process Framework not Supported."); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Process Framework Name = " + processConfig.framework.name); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeExchangeAPI -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - sessionStopped() - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeContext -> onInizialized -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function intitializeProcessFramework() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> Entering function."); } - - processFramework.initialize(processConfig, statusDependencies, dataDependencies, exchangeAPI, onInizialized); - - function onInizialized(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well."); - startProcessFramework(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> onInizialized -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - sessionStopped() - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - case global.CUSTOM_OK_RESPONSE.result: { - - switch (err.message) { - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function startProcessFramework() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> Entering function."); } - - processFramework.start(onFinished); - - function onFinished(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Entering function."); } - processFramework.finalize() - processFramework = undefined - dataDependencies.finalize() - dataDependencies = undefined - statusDependencies.finalize() - statusDependencies = undefined - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well."); - nextWaitTime = 'Normal'; - finishProcessExecutionEvents() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - sessionStopped() - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - case global.CUSTOM_OK_RESPONSE.result: { - - switch (err.message) { - case "Dependency does not exist.": { - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep."); - nextWaitTime = 'Sleep'; - loopControl(nextWaitTime); - return; - } - case "Dependency not ready.": { - logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep."); - nextWaitTime = 'Sleep'; - loopControl(nextWaitTime); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - } - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = "+ err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function finishProcessExecutionEvents() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> Entering function."); } - - processExecutionEvents.finish(onFinished); - - function onFinished(err) { - try { - processExecutionEvents.finalize() - processExecutionEvents = undefined - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well."); - nextWaitTime = 'Normal'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - processStopped() - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process."); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result); - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message); - - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function loopControl(nextWaitTime) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Entering function."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); } - - /* We show we reached the end of the loop. */ - - processHeartBeat() - - /* Here we check if we must stop the loop gracefully. */ - - shallWeStop(onStop, onContinue); - - function onStop() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Entering function."); } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); } - - if (global.WRITE_LOGS_TO_FILES === 'true') { - logger.persist(); - } - - global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped') - processStopped() - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - - } - - function onContinue() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onContinue -> Entering function."); } - - /* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */ - - switch (nextWaitTime) { - case 'Normal': { - let waitTime - if (bot.startMode === "Live") { - waitTime = processConfig.liveWaitTime - } else { - waitTime = processConfig.normalWaitTime - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s."); } - if (processConfig.deadWaitTime > 0) { - checkLoopHealthHandle = setTimeout(checkLoopHealth, processConfig.deadWaitTime, bot.loopCounter); - } - nextLoopTimeoutHandle = setTimeout(loop, waitTime); - let waitingTime = waitTime / 1000 / 60 - let label = 'minute/s' - if (waitingTime < 1) { - waitingTime = waitTime / 1000 - label = 'seconds' - } - processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for next execution.") - if (global.WRITE_LOGS_TO_FILES === 'true') { - logger.persist(); - } - } - break; - case 'Retry': { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); } - if (processConfig.deadWaitTime > 0) { - checkLoopHealthHandle = setTimeout(checkLoopHealth, processConfig.deadWaitTime, bot.loopCounter); - } - nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime); - processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.") - logger.persist(); - } - break; - case 'Sleep': { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); } - nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.") - logger.persist(); - } - break; - case 'Coma': { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); } - nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime); - processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.") - logger.persist(); - } - break; - } - } - } - - function checkLoopHealth(pLastLoop) { - - if (bot.enableCheckLoopHealth === false || global.STOP_TASK_GRACEFULLY === true) { - - logger.write(MODULE_NAME, "[WARN] run -> loop -> checkLoopHealth -> bot.enableCheckLoopHealth = " + bot.enableCheckLoopHealth); - - return; - } // This gets disabled anytime the Main Loop is shut down by any condition. - - if (bot.loopCounter <= pLastLoop + 1) { // This means that the next loop started but also stopped executing abruptally. - - let now = new Date().valueOf(); - - if (now - bot.loopStartTime > processConfig.deadWaitTime) { - - logger.write(MODULE_NAME, "[ERROR] run -> loop -> checkLoopHealth -> Dead loop found -> pLastLoop = " + pLastLoop); - console.log((new Date().toISOString() + " [ERROR] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found dead. Resurrecting it now.")); - - logger.persist(); // We persist the logs of the failed execution. - clearTimeout(nextLoopTimeoutHandle); // We cancel the timeout in case the original loop was still running and schedulled to reexecute. - loop(); // We restart the loop so that the processing can continue. - - } else { - - console.log((new Date().toISOString() + " [WARN] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found delayed but still alive. No action taken.")); - - } - } - } - - function shallWeStop(stopCallBack, continueCallBack) { - - try { - - /* IMPORTANT: This function is exactly the same on the 3 modules. */ - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> shallWeStop -> Entering function. "); } - - if (!global.STOP_TASK_GRACEFULLY) { - continueCallBack(); - } else { - stopCallBack(); - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack); - logger.persist(); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = "+ err.stack); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(err); - } - } - - function processHeartBeat(processingDate, percentage, status) { - let event = { - seconds: (new Date()).getSeconds(), - processingDate: processingDate, - percentage: percentage, - status: status - } - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) - } - - function sessionHeartBeat(processingDate) { - let event = { - seconds: (new Date()).getSeconds(), - processingDate: processingDate - } - global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Heartbeat', event) - - if (global.STOP_TASK_GRACEFULLY === true) { - bot.STOP_SESSION = true - } - } - - function processStopped() { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') - } - - function sessionStopped() { - if (bot.SESSION_STATUS === 'Running') { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped') - bot.SESSION_STATUS = 'Stopped' - } - } - - } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] run -> err = "+ err.stack); - clearInterval(fixedTimeLoopIntervalHandle); - clearTimeout(nextLoopTimeoutHandle); - clearTimeout(checkLoopHealthHandle); - bot.enableCheckLoopHealth = false; - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } -}; diff --git a/TaskServer/TradingSimulation.js b/TaskServer/TradingSimulation.js deleted file mode 100644 index 2568465d7..000000000 --- a/TaskServer/TradingSimulation.js +++ /dev/null @@ -1,3381 +0,0 @@ -exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; - const MODULE_NAME = "Trading Simulation -> " + bot.SESSION.name; - - const GMT_SECONDS = ':00.000 GMT+0000'; - - let thisObject = { - finalize: finalize, - runSimulation: runSimulation - }; - - let utilities = UTILITIES.newCloudUtilities(bot, logger); - - return thisObject; - - function finalize() { - thisObject = undefined - } - - function runSimulation( - chart, - dataDependencies, - timeFrame, - timeFrameLabel, - currentDay, - interExecutionMemory, - exchangeAPI, - callback, - callBackFunction) { - - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> Entering function."); } - let processingDailyFiles - if (timeFrame > global.dailyFilePeriods[0][0]) { - processingDailyFiles = false - } else { - processingDailyFiles = true - } - - let recordsArray = []; - let conditionsArray = []; - let strategiesArray = []; - let tradesArray = []; - - let tradingSystem = bot.TRADING_SYSTEM - - /* Initial Default Values */ - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.initialDatetime = " + bot.VALUES_TO_USE.timeRange.initialDatetime); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.finalDatetime = " + bot.VALUES_TO_USE.timeRange.finalDatetime); } - - let timerToCloseStage = 0 - - /* Stop Loss Management */ - - const MIN_STOP_LOSS_VALUE = 0.0000000001 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result. - const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER - let stopLoss = 0; - - /* Take Profit Management */ - - const MIN_TAKE_PROFIT_VALUE = 0.0000000001 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result. - const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER - let takeProfit = 0; - - /* Simulation Records */ - - let hitRatio = 0; - let ROI = 0; - let days = 0; - let positionDays = 0; - let anualizedRateOfReturn = 0; - let type = '""'; - let marketRate = 0; - let takePositionNow = false - let closePositionNow = false - - /* In some cases we need to know if we are positioned at the last candle of the calendar day, for that we need these variables. */ - - let lastInstantOfTheDay = 0 - if (currentDay) { - lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1; - } - - /* - The following counters need to survive multiple executions of the similator and keep themselves reliable. - This is challenging when the simulation is executed using Daily Files, since each execution means a new - day and these counters are meant to be kept along the whole market. - - To overcome this problem, we use the interExecutionMemory to record the values of the current execution - when finish. But there are a few details: - - 1. When the process is at the head of the market, it executes multple times at the same day. - 2. The same code serves execution from Market Files. - 3. In Daily Files we are receiving candles from the current day and previous day, so we need to take care of - not adding to the counters duplicate info when processing the day before candles. - - To overcome these challenges we record the values of the counters and variables on the interExecutionMemory only when - the day is complete and if we have a current Day. That menas that for Market Files we will never use - interExecutionMemory. - */ - - /* Snapshots of Trigger On and Take Positions */ - let snapshots = { - headers: undefined, - triggerOn: [], - takePosition: [], - lastTriggerOn: undefined, - lastTakePosition: undefined - } - - /*Needed for statistics */ - let previousBalanceBaseAsset = 0; - let previousBalanceQuotedAsset = 0; - - /* Position Management */ - let tradePositionRate = 0; - let tradePositionSize = 0; - - /* Strategy and Phase Management */ - let currentStrategyIndex = -1; - let strategyStage = 'No Stage'; - - let stopLossPhase = -1; - let stopLossStage = 'No Stage'; - - let takeProfitPhase = -1; - let takeProfitStage = 'No Stage'; - - /* These 2 objects will allow us to create separate files for each one of them. */ - - let currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '', - name: '' - } - - let currentTrade = { - begin: 0, - end: 0, - status: 0, - profit: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - let balanceBaseAsset = bot.VALUES_TO_USE.initialBalanceA; - let balanceQuotedAsset = bot.VALUES_TO_USE.initialBalanceB; - - let lastTradeProfitLoss = 0; - let profit = 0; - let lastTradeROI = 0; - - let roundtrips = 0; - let fails = 0; - let hits = 0; - let periods = 0; - let positionPeriods = 0; - - let closeRate - - /* Usefull counters for conditions and formulas */ - - let distanceToLast = { - triggerOn: 0, - triggerOff: 0, - takePosition: 0, - closePosition: 0 - } - - /* Message to the Simulation Executor */ - - let orderId = 0; - let messageId = 0; - - /* Allowing these to be accesible at formulas */ - baseAsset = bot.VALUES_TO_USE.baseAsset - quotedAsset = bot.VALUES_TO_USE.quotedAsset - - let yesterday = {}; - - /* Initialization */ - - - yesterday.snapshots = { - headers: undefined, - triggerOn: [], - takePosition: [], - lastTriggerOn: undefined, - lastTakePosition: undefined - } - - yesterday.stopLoss = 0 - yesterday.takeProfit = 0 - - yesterday.previousBalanceBaseAsset = previousBalanceBaseAsset - yesterday.previousBalanceQuotedAsset = previousBalanceQuotedAsset - - yesterday.tradePositionRate = tradePositionRate - yesterday.tradePositionSize = tradePositionSize - - yesterday.currentStrategyIndex = currentStrategyIndex - yesterday.strategyStage = strategyStage - - yesterday.stopLossPhase = stopLossPhase - yesterday.stopLossStage = stopLossStage - - yesterday.takeProfitPhase = takeProfitPhase - yesterday.takeProfitStage = takeProfitStage - - yesterday.currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '', - name: '' - } - - yesterday.currentTrade = { - begin: 0, - end: 0, - status: 0, - profit: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - yesterday.lastTradeProfitLoss = 0; - yesterday.profit = 0; - yesterday.lastTradeROI = 0; - - yesterday.roundtrips = 0; - yesterday.fails = 0; - yesterday.hits = 0; - yesterday.periods = 0; - yesterday.positionPeriods = 0; - - yesterday.distanceToLast = { - triggerOn: 0, - triggerOff: 0, - takePosition: 0, - closePosition: 0 - } - - yesterday.orderId = 0; - yesterday.messageId = 0; - - yesterday.hitRatio = 0; - yesterday.ROI = 0; - yesterday.anualizedRateOfReturn = 0; - - if (interExecutionMemory.roundtrips === undefined) { // This just means that the inter execution memory was never used before. - - /* Initialize the data structure we will use inter execution. */ - - interExecutionMemory.snapshots = { - headers: undefined, - triggerOn: [], - takePosition: [], - lastTriggerOn: undefined, - lastTakePosition: undefined - } - - interExecutionMemory.stopLoss = 0 - interExecutionMemory.takeProfit = 0 - - interExecutionMemory.previousBalanceBaseAsset = previousBalanceBaseAsset - interExecutionMemory.previousBalanceQuotedAsset = previousBalanceQuotedAsset - - interExecutionMemory.tradePositionRate = tradePositionRate - interExecutionMemory.tradePositionSize = tradePositionSize - - interExecutionMemory.currentStrategyIndex = currentStrategyIndex - interExecutionMemory.strategyStage = strategyStage - - interExecutionMemory.stopLossPhase = stopLossPhase - interExecutionMemory.stopLossStage = stopLossStage - - interExecutionMemory.takeProfitPhase = takeProfitPhase - interExecutionMemory.takeProfitStage = takeProfitStage - - interExecutionMemory.currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '', - name: '' - } - - interExecutionMemory.currentTrade = { - begin: 0, - end: 0, - status: 0, - profit: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - interExecutionMemory.balanceBaseAsset = balanceBaseAsset; - interExecutionMemory.balanceQuotedAsset = balanceQuotedAsset; - - interExecutionMemory.lastTradeProfitLoss = lastTradeProfitLoss; - interExecutionMemory.profit = profit; - interExecutionMemory.lastTradeROI = lastTradeROI; - - interExecutionMemory.roundtrips = 0; - interExecutionMemory.fails = 0; - interExecutionMemory.hits = 0; - interExecutionMemory.periods = 0; - interExecutionMemory.positionPeriods = 0; - - interExecutionMemory.distanceToLast = { - triggerOn: 0, - triggerOff: 0, - takePosition: 0, - closePosition: 0 - } - - interExecutionMemory.orderId = 0; - interExecutionMemory.messageId = 0; - - interExecutionMemory.hitRatio = 0; - interExecutionMemory.ROI = 0; - interExecutionMemory.anualizedRateOfReturn = 0; - - interExecutionMemory.announcements = [] - - } else { - - /* We get the initial values from the day previous to the candles we receive at the current execution */ - - snapshots = { - headers: interExecutionMemory.snapshots.headers, - triggerOn: interExecutionMemory.snapshots.triggerOn, - takePosition: interExecutionMemory.snapshots.takePosition, - lastTriggerOn: interExecutionMemory.snapshots.lastTriggerOn, - lastTakePosition: interExecutionMemory.snapshots.lastTakePosition - } - - stopLoss = interExecutionMemory.stopLoss - takeProfit = interExecutionMemory.takeProfit - - previousBalanceBaseAsset = interExecutionMemory.previousBalanceBaseAsset - previousBalanceQuotedAsset = interExecutionMemory.previousBalanceQuotedAsset - - tradePositionRate = interExecutionMemory.tradePositionRate - tradePositionSize = interExecutionMemory.tradePositionSize - - currentStrategyIndex = interExecutionMemory.currentStrategyIndex - strategyStage = interExecutionMemory.strategyStage - - stopLossPhase = interExecutionMemory.stopLossPhase - stopLossStage = interExecutionMemory.stopLossStage - - takeProfitPhase = interExecutionMemory.takeProfitPhase - takeProfitStage = interExecutionMemory.takeProfitStage - - currentStrategy = { - begin: interExecutionMemory.currentStrategy.begin, - end: interExecutionMemory.currentStrategy.end, - status: interExecutionMemory.currentStrategy.status, - number: interExecutionMemory.currentStrategy.number, - beginRate: interExecutionMemory.currentStrategy.beginRate, - endRate: interExecutionMemory.currentStrategy.endRate, - triggerOnSituation: interExecutionMemory.currentStrategy.triggerOnSituation, - name: currentStrategy.name - } - - currentTrade = { - begin: interExecutionMemory.currentTrade.begin, - end: interExecutionMemory.currentTrade.end, - status: interExecutionMemory.currentTrade.status, - profit: interExecutionMemory.currentTrade.profit, - exitType: interExecutionMemory.currentTrade.exitType, - beginRate: interExecutionMemory.currentTrade.beginRate, - endRate: interExecutionMemory.currentTrade.endRate, - takePositionSituation: interExecutionMemory.currentTrade.takePositionSituation - } - - if (currentDay) { - if (currentDay.valueOf() >= bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf() + ONE_DAY_IN_MILISECONDS) { // Only after the first day we start grabbing the balance from this memory. - - balanceBaseAsset = interExecutionMemory.balanceBaseAsset; - balanceQuotedAsset = interExecutionMemory.balanceQuotedAsset; - - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - } - } - - lastTradeProfitLoss = interExecutionMemory.lastTradeProfitLoss; - profit = interExecutionMemory.profit; - lastTradeROI = interExecutionMemory.lastTradeROI; - - roundtrips = interExecutionMemory.roundtrips; - fails = interExecutionMemory.fails; - hits = interExecutionMemory.hits; - periods = interExecutionMemory.periods; - positionPeriods = interExecutionMemory.positionPeriods; - - distanceToLast.triggerOn = interExecutionMemory.distanceToLast.triggerOn - distanceToLast.triggerOff = interExecutionMemory.distanceToLast.triggerOff - distanceToLast.takePosition = interExecutionMemory.distanceToLast.takePosition - distanceToLast.closePosition = interExecutionMemory.distanceToLast.closePosition - - orderId = interExecutionMemory.orderId; // to be deprecated - messageId = interExecutionMemory.messageId; // to be deprecated - - hitRatio = interExecutionMemory.hitRatio; - ROI = interExecutionMemory.ROI; - anualizedRateOfReturn = interExecutionMemory.anualizedRateOfReturn; - - /* For the case that any of these variables are not updated during the main loop, we need to store their value at the yesterday structure, otherwise it would be lost. */ - - yesterday.snapshots = { - headers: snapshots.headers, - triggerOn: snapshots.triggerOn, - takePosition: snapshots.takePosition, - lastTriggerOn: snapshots.lastTriggerOn, - lastTakePosition: snapshots.lastTakePosition - } - - yesterday.stopLoss = stopLoss - yesterday.takeProfit = takeProfit - - yesterday.previousBalanceBaseAsset = previousBalanceBaseAsset - yesterday.previousBalanceQuotedAsset = previousBalanceQuotedAsset - - yesterday.tradePositionRate = tradePositionRate - yesterday.tradePositionSize = tradePositionSize - - yesterday.currentStrategyIndex = currentStrategyIndex; - yesterday.strategyStage = strategyStage; - - yesterday.stopLossPhase = stopLossPhase; - yesterday.stopLossStage = stopLossStage; - - yesterday.takeProfitPhase = takeProfitPhase; - yesterday.takeProfitStage = takeProfitStage; - - yesterday.currentStrategy = { - begin: currentStrategy.begin, - end: currentStrategy.end, - status: currentStrategy.status, - number: currentStrategy.number, - beginRate: currentStrategy.beginRate, - endRate: currentStrategy.endRate, - triggerOnSituation: currentStrategy.triggerOnSituation, - name: currentStrategy.name - } - - yesterday.currentTrade = { - begin: currentTrade.begin, - end: currentTrade.end, - status: currentTrade.status, - profit: currentTrade.profit, - exitType: currentTrade.exitType, - beginRate: currentTrade.beginRate, - endRate: currentTrade.endRate, - takePositionSituation: currentTrade.takePositionSituation - } - - yesterday.lastTradeProfitLoss = lastTradeProfitLoss; - yesterday.profit = profit; - yesterday.lastTradeROI = lastTradeROI; - - yesterday.roundtrips = roundtrips; - yesterday.fails = fails; - yesterday.hits = hits; - yesterday.periods = periods; - yesterday.positionPeriods = positionPeriods; - - yesterday.distanceToLast.triggerOn = distanceToLast.triggerOn - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - yesterday.distanceToLast.takePosition = distanceToLast.takePosition - yesterday.distanceToLast.closePosition = distanceToLast.closePosition - - yesterday.hitRatio = hitRatio; - yesterday.ROI = ROI; - yesterday.anualizedRateOfReturn = anualizedRateOfReturn; - } - - /* Main Array and Maps */ - - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - let candles = chart[propertyName].candles - let currentChart = chart[propertyName] - - /* Last Candle */ - let lastCandle = candles[candles.length - 1]; - - /* Main Simulation Loop: We go thourgh all the candles at this time period. */ - let currentCandleIndex - - /* For Loop Level heartbeat */ - let loopingDay - let previousLoopingDay - - initializeLoop() - - function initializeLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> initializeLoop -> Entering function."); } - - /* Estimate Initial Candle */ - - let firstEnd = candles[0].end - let targetEnd = bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf() - let diff = targetEnd - firstEnd - let amount = diff / timeFrame - - currentCandleIndex = Math.trunc(amount) - if (currentCandleIndex < 0) { currentCandleIndex = 0 } - if (currentCandleIndex > candles.length - 1) { - /* This will happen when the bot.VALUES_TO_USE.timeRange.initialDatetime is beyond the last candle available, meaning that the dataSet needs to be updated with more up-to-date data. */ - currentCandleIndex = candles.length - 1 - } - - loop() - } - - function loop() { - - 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 = [] - saveAsLastTriggerOnSnapshot = false - saveAsLastTakePositionSnapshot = false - addToSnapshots = false - - let announcementsToBeMade = [] - let candle = candles[currentCandleIndex]; - - /* Not processing while out of user-defined time range */ - - if (candle.end < bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle before the bot.VALUES_TO_USE.timeRange.initialDatetime."); } - controlLoop(); - return - } - if (candle.begin > bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle after the bot.VALUES_TO_USE.timeRange.finalDatetime."); } - afterLoop(); - return - } - - /* Here we update at the chart data structure the objects for each product representing where we are currently standing at the simulation loop */ - if (processingDailyFiles) { - for (let j = 0; j < global.dailyFilePeriods.length; j++) { - - let mapKey = dailyFilePeriods[j][1] - let propertyName = 'at' + mapKey.replace('-', ''); - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Daily') { continue } - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], candle, 'Daily' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement - } - } - } - - /* Finding the Current Element on Market Files */ - for (let j = 0; j < global.marketFilesPeriods.length; j++) { - - let mapKey = marketFilesPeriods[j][1] - let propertyName = 'at' + mapKey.replace('-', ''); - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Market') { continue } - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], candle, 'Market' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement - } - } - - /* Finding the Current Element on Single Files */ - - function isItInside(elementWithTimestamp, elementWithBeginEnd) { - if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) { - return true - } else { - return false - } - } - - let propertyName = 'atAnyTimeFrame' - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Single-File') { continue } - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let elementArray = thisChart[pluralVariableName] - let currentElement - if (elementArray !== undefined) { - currentElement = elementArray[elementArray.length - 1] - } - thisChart[singularVariableName] = currentElement - } - - /* While we are processing the previous day. */ - let positionedAtYesterday = false - if (currentDay) { - positionedAtYesterday = (candle.end < currentDay.valueOf()) - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Candle Begin @ " + (new Date(candle.begin)).toLocaleString()) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Candle End @ " + (new Date(candle.end)).toLocaleString()) } - - let ticker = { - bid: candle.close, - ask: candle.close, - last: candle.close - } - - /* We will produce a simulation level heartbeat in order to inform the user this is running. */ - - loopingDay = new Date(Math.trunc(candle.begin / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - if (loopingDay.valueOf() !== previousLoopingDay) { - - let processingDate = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Simulation " + bot.sessionKey + " Loop # " + currentCandleIndex + " @ " + processingDate) } - - /* Telling the world we are alive and doing well */ - let fromDate = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) - let lastDate = new Date(bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) - - let currentDateString = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2); - let currentDate = new Date(loopingDay) - let percentage = global.getPercentage(fromDate, currentDate, lastDate) - bot.processHeartBeat(currentDateString, percentage) - - if (global.areEqualDates(currentDate, new Date()) === false) { - logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage); - } - } - previousLoopingDay = loopingDay.valueOf() - - /* If any of the needed data dependencies is missing for this particular candle, then we jump the candle*/ - /* - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let product = currentChart[pluralVariableName] - let currentElement = currentChart[singularVariableName] - - if (currentElement === undefined) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because " + singularVariableName + " is undefined."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> For some indicators it is normal to start after a few candles."); } - controlLoop(); - return - } - } - */ - - periods++; - days = periods * timeFrame / ONE_DAY_IN_MILISECONDS; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.periods = periods - } - - /* We skip the candle at the head of the market because currentCandleIndex has not closed yet. */ - let candlesPerDay = ONE_DAY_IN_MILISECONDS / timeFrame - if (currentCandleIndex === candles.length - 1) { - if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { - /*We are at the head of the market, thus we skip the last candle because it has not close yet. */ - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet."); } - controlLoop(); - return - /* Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators. - if we skip these two periods, then there will be a hole in the file since the last period will be missing. */ - } - } - - } else { // We are processing Market Files - if (currentCandleIndex === candles.length - 1) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet."); } - controlLoop(); - return - } - } - - let conditions = new Map; // Here we store the conditions values that will be use in the simulator for decision making. - let formulas = new Map; - let conditionsArrayRecord = []; // These are the records that will be saved in a file for the plotter to consume. - let conditionsArrayValues = []; // Here we store the conditions values that will be written on file for the plotter. - let formulasErrors = []; // Here we store the errors produced by all phase formulas. - let formulasValues = []; // Here we store the values produced by all phase formulas. - - /* We define and evaluate all conditions to be used later during the simulation loop. */ - - conditionsArrayRecord.push(candle.begin); - conditionsArrayRecord.push(candle.end); - - for (let j = 0; j < tradingSystem.strategies.length; j++) { - - let strategy = tradingSystem.strategies[j]; - - let positionSize = 0 - let positionRate = 0 - - /* Continue with the rest of the formulas and conditions */ - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.triggerOn !== undefined) { - - for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { - - let situation = triggerStage.triggerOn.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - if (triggerStage.triggerOff !== undefined) { - - for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { - - let situation = triggerStage.triggerOff.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - if (triggerStage.takePosition !== undefined) { - - for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { - - let situation = triggerStage.takePosition.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - - let openStage = strategy.openStage - - if (openStage !== undefined) { - - /* Default Values*/ - if (baseAsset === bot.market.baseAsset) { - positionSize = balanceBaseAsset; - positionRate = candle.close; - } else { - positionSize = balanceQuotedAsset; - positionRate = candle.close; - } - - let initialDefinition = openStage.initialDefinition - - if (initialDefinition !== undefined) { - - if (tradePositionSize !== 0) { - positionSize = tradePositionSize - } else { - if (initialDefinition.positionSize !== undefined) { - if (initialDefinition.positionSize.formula !== undefined) { - try { - let code = initialDefinition.positionSize.formula.code - positionSize = eval(code); - addCodeToSnapshot(code) - } catch (err) { - initialDefinition.positionSize.formula.error = err.message - } - if (isNaN(positionSize)) { - if (baseAsset === bot.market.baseAsset) { - positionSize = balanceBaseAsset; - } else { - positionSize = balanceQuotedAsset; - } - } else { - if (baseAsset === bot.market.baseAsset) { - if (positionSize > balanceBaseAsset) { positionSize = balanceBaseAsset } - } else { - if (positionSize > balanceQuotedAsset) { positionSize = balanceQuotedAsset } - } - } - } - } - } - - if (tradePositionRate !== 0) { - positionRate = tradePositionRate - } else { - 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 - } - if (isNaN(positionRate)) { - if (baseAsset === bot.market.baseAsset) { - positionRate = candle.close; - } else { - positionRate = candle.close; - } - } - } - } - } - - if (initialDefinition.stopLoss !== undefined) { - - for (let p = 0; p < initialDefinition.stopLoss.phases.length; p++) { - - let phase = initialDefinition.stopLoss.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - let code = phase.formula.code - formulaValue = eval(code); - addCodeToSnapshot(code) - if (formulaValue === Infinity) { - formulaError = "Formula evaluates to Infinity." - formulaValue = MAX_STOP_LOSS_VALUE - if (stopLossStage === 'Open Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_STOP_LOSS_VALUE) { - formulaValue = MIN_STOP_LOSS_VALUE - if (stopLossStage === 'Open Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - - if (initialDefinition.takeProfit !== undefined) { - - for (let p = 0; p < initialDefinition.takeProfit.phases.length; p++) { - - let phase = initialDefinition.takeProfit.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - let code = phase.formula.code - formulaValue = eval(code); - addCodeToSnapshot(code) - if (formulaValue === Infinity) { - formulaValue = MAX_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Open Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_TAKE_PROFIT_VALUE) { - formulaValue = MIN_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Open Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - } - - strategy.positionSize = positionSize - strategy.positionRate = positionRate - } - - let manageStage = strategy.manageStage - - if (manageStage !== undefined) { - - if (manageStage.stopLoss !== undefined) { - - for (let p = 0; p < manageStage.stopLoss.phases.length; p++) { - - let phase = manageStage.stopLoss.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - let code = phase.formula.code - formulaValue = eval(code); - addCodeToSnapshot(code) - if (formulaValue === Infinity) { - formulaError = "" - formulaValue = MAX_STOP_LOSS_VALUE - if (stopLossStage === 'Manage Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_STOP_LOSS_VALUE) { - formulaValue = MIN_STOP_LOSS_VALUE - if (stopLossStage === 'Manage Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - - if (manageStage.takeProfit !== undefined) { - - for (let p = 0; p < manageStage.takeProfit.phases.length; p++) { - - let phase = manageStage.takeProfit.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - let code = phase.formula.code - formulaValue = eval(code); - addCodeToSnapshot(code) - if (formulaValue === Infinity) { - formulaError = "Formula evaluates to Infinity." - formulaValue = MAX_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Manage Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_TAKE_PROFIT_VALUE) { - formulaValue = MIN_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Manage Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - } - - function newCondition(key, node, chart) { - - let condition; - let error = '' - let value - - try { - 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 - reason and others, we will simply set the value to false. - */ - value = false - - if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || - node.code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1 - ) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - node.error = err.message + " @ " + (new Date(candle.begin)).toLocaleString() - } - } - - condition = { - key: key, - value: value - }; - - conditions.set(condition.key, condition); - - if (condition.value) { - conditionsArrayValues.push(1); - } else { - conditionsArrayValues.push(0); - } - } - } - - /* Trigger On Conditions */ - if ( - strategyStage === 'No Stage' && - currentStrategyIndex === -1 - ) { - let minimumBalance - let maximumBalance - let balance - - if (baseAsset === bot.market.baseAsset) { - balance = balanceBaseAsset - minimumBalance = bot.VALUES_TO_USE.minimumBalanceA - maximumBalance = bot.VALUES_TO_USE.maximumBalanceA - } else { - balance = balanceQuotedAsset - minimumBalance = bot.VALUES_TO_USE.minimumBalanceB - maximumBalance = bot.VALUES_TO_USE.maximumBalanceB - } - - let stopRunningDate = new Date(candle.begin) - if (balance <= minimumBalance) { - tradingSystem.error = "Min Balance @ " + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> " + tradingSystem.error); } - afterLoop() - return - } - - if (balance >= maximumBalance) { - tradingSystem.error = "Max Balance @ " + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> " + tradingSystem.error); } - afterLoop() - return - } - - /* - Here we need to pick a strategy, or if there is not suitable strategy for the current - market conditions, we pass until the next period. - - To pick a new strategy we will evaluate what we call the trigger on. Once we enter - into one strategy, we will ignore market conditions for others. However there is also - a strategy trigger off which can be hit before taking a position. If hit, we would - be outside a strategy again and looking for the condition to enter all over again. - - */ - - for (let j = 0; j < tradingSystem.strategies.length; j++) { - - if ( - strategyStage !== 'No Stage' || - currentStrategyIndex !== -1 - ) { continue } - - let strategy = tradingSystem.strategies[j]; - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.triggerOn !== undefined) { - - for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { - - let situation = triggerStage.triggerOn.situations[k]; - let passed - if (situation.conditions.length > 0) { - passed = true - } - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - strategyStage = 'Trigger Stage'; - checkAnnouncements(triggerStage) - - currentStrategyIndex = j; - currentStrategy.begin = candle.begin; - currentStrategy.beginRate = candle.min; - currentStrategy.endRate = candle.min; // In case the strategy does not get exited - currentStrategy.triggerOnSituation = situation.name - currentStrategy.name = strategy.name - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.currentStrategyIndex = currentStrategyIndex; - yesterday.currentStrategy.begin = currentStrategy.begin; - yesterday.currentStrategy.beginRate = currentStrategy.beginRate; - yesterday.currentStrategy.endRate = currentStrategy.endRate; - yesterday.currentStrategy.triggerOnSituation = currentStrategy.triggerOnSituation; - yesterday.currentStrategy.name = currentStrategy.name; - } - } - - distanceToLast.triggerOn = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOn = distanceToLast.triggerOn - } - } - - checkAnnouncements(triggerStage.triggerOn) - saveAsLastTriggerOnSnapshot = 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; - } - } - } - } - } - - } - - /* Trigger Off Condition */ - if (strategyStage === 'Trigger Stage') { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.triggerOff !== undefined) { - - for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { - - let situation = triggerStage.triggerOff.situations[k]; - let passed - if (situation.conditions.length > 0) { - passed = true - } - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = currentStrategyIndex + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - currentStrategy.number = currentStrategyIndex - currentStrategy.end = candle.end; - currentStrategy.endRate = candle.min; - currentStrategy.status = 1; // This means the strategy is closed, i.e. that has a begin and end. - strategyStage = 'No Stage'; - currentStrategyIndex = -1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentStrategy.number = currentStrategy.number; - yesterday.currentStrategy.end = currentStrategy.end; - yesterday.currentStrategy.endRate = currentStrategy.endRate; - yesterday.currentStrategy.status = currentStrategy.status; - yesterday.strategyStage = strategyStage; - yesterday.currentStrategyIndex = currentStrategyIndex; - } - } - - distanceToLast.triggerOff = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - } - } - - checkAnnouncements(triggerStage.triggerOff) - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Switching to No Stage because conditions at the Trigger Off Event were met."); } - break; - } - } - } - } - } - - /* Take Position Condition */ - if (strategyStage === 'Trigger Stage') { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.takePosition !== undefined) { - - for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { - - let situation = triggerStage.takePosition.situations[k]; - let passed - if (situation.conditions.length > 0) { - passed = true - } - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = currentStrategyIndex + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - type = '"Take Position"'; - - strategyStage = 'Open Stage'; - checkAnnouncements(strategy.openStage) - - stopLossStage = 'Open Stage'; - takeProfitStage = 'Open Stage'; - stopLossPhase = 0; - takeProfitPhase = 0; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - yesterday.stopLossPhase = stopLossPhase; - yesterday.takeProfitPhase = takeProfitPhase; - } - } - - takePositionNow = true - currentTrade.takePositionSituation = situation.name - - checkAnnouncements(triggerStage.takePosition) - saveAsLastTakePositionSnapshot = true - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Conditions at the Take Position Event were met."); } - break; - } - } - } - } - } - - /* Stop Loss Management */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') && - takePositionNow !== true - ) { - - checkStopPhases() - calculateStopLoss(); - - } - - function checkStopPhases() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let j = currentStrategyIndex - let stageKey - let initialDefinitionKey = '' - let p - - if (stopLossStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.stopLoss !== undefined) { - parentNode = openStage.initialDefinition - initialDefinitionKey = '-' + 'initialDefinition' - stageKey = 'openStage' - p = stopLossPhase - } - } - } - - if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.stopLoss !== undefined) { - parentNode = manageStage - stageKey = 'manageStage' - p = stopLossPhase - 1 - } - } - - if (parentNode !== undefined) { - let phase = parentNode.stopLoss.phases[p]; - - /* Check the next Phase Event. */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - let passed - if (situation.conditions.length > 0) { - passed = true - } - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - stopLossPhase++; - stopLossStage = 'Manage Stage' - if (takeProfitPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Take Profit') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLossPhase = stopLossPhase; - yesterday.stopLossStage = stopLossStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(nextPhaseEvent) - return; - } - } - } - - /* Check the Move to Phase Events. */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - let passed - if (situation.conditions.length > 0) { - passed = true - } - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < parentNode.stopLoss.phases.length; q++) { - if (parentNode.stopLoss.phases[q].id === moveToPhase.id) { - stopLossPhase = q + 1 - } - } - } else { - moveToPhaseEvent.error = "This Node needs to reference a Phase." - continue - } - - stopLossStage = 'Manage Stage' - if (takeProfitPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Take Profit') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLossPhase = stopLossPhase; - yesterday.stopLossStage = stopLossStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(moveToPhaseEvent) - return; - } - } - } - } - } - } - - function calculateStopLoss() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - let key - - if (stopLossStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.stopLoss !== undefined) { - phase = openStage.initialDefinition.stopLoss.phases[stopLossPhase]; - key = currentStrategyIndex + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + (stopLossPhase); - } - } - } - - if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.stopLoss !== undefined) { - phase = manageStage.stopLoss.phases[stopLossPhase - 1]; - key = currentStrategyIndex + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + (stopLossPhase - 1); - } - } - - if (phase !== undefined) { - if (phase.formula !== undefined) { - let previousValue = stopLoss - - stopLoss = formulas.get(key) - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - - if (stopLoss !== previousValue) { - checkAnnouncements(phase, stopLoss) - } - } - } - } - - /* Take Profit Management */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') && - takePositionNow !== true - ) { - - checkTakeProfitPhases(); - calculateTakeProfit(); - - } - - function checkTakeProfitPhases() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let j = currentStrategyIndex - let stageKey - let initialDefinitionKey = '' - let p - - if (takeProfitStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.takeProfit !== undefined) { - parentNode = openStage.initialDefinition - initialDefinitionKey = '-' + 'initialDefinition' - stageKey = 'openStage' - p = takeProfitPhase - } - } - } - - if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.takeProfit !== undefined) { - parentNode = manageStage - stageKey = 'manageStage' - p = takeProfitPhase - 1 - } - } - - if (parentNode !== undefined) { - let phase = parentNode.takeProfit.phases[p]; - if (phase === undefined) { return } // trying to jump to a phase that does not exists. - - /* Check the next Phase Event. */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - let passed - if (situation.conditions.length > 0) { - passed = true - } - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - takeProfitPhase++; - takeProfitStage = 'Manage Stage' - if (stopLossPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Stop') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfitPhase = takeProfitPhase; - yesterday.takeProfitStage = takeProfitStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(nextPhaseEvent) - return; - } - } - } - - /* Check the Move to Phase Events. */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - let passed - if (situation.conditions.length > 0) { - passed = true - } - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < parentNode.takeProfit.phases.length; q++) { - if (parentNode.takeProfit.phases[q].id === moveToPhase.id) { - takeProfitPhase = q + 1 - } - } - } else { - moveToPhaseEvent.error = "This Node needs to reference a Phase." - continue - } - - takeProfitStage = 'Manage Stage' - if (stopLossPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Stop') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfitPhase = takeProfitPhase; - yesterday.takeProfitStage = takeProfitStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(moveToPhaseEvent) - return; - } - } - } - } - } - } - - function calculateTakeProfit() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - let key - - if (takeProfitStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.takeProfit !== undefined) { - phase = openStage.initialDefinition.takeProfit.phases[takeProfitPhase]; - key = currentStrategyIndex + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + (takeProfitPhase); - } - } - } - - if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.takeProfit !== undefined) { - phase = manageStage.takeProfit.phases[takeProfitPhase - 1]; - key = currentStrategyIndex + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + (takeProfitPhase - 1); - } - } - - if (phase !== undefined) { - if (phase.formula !== undefined) { - - let previousValue = stopLoss - - takeProfit = formulas.get(key) - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - - if (takeProfit !== previousValue) { - checkAnnouncements(phase, takeProfit) - } - } - } - } - - /* Keeping Position Counters Up-to-date */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') - ) { - - if (takePositionNow === true) { - positionPeriods = 0 - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.positionPeriods = 0 - } - } - } - - positionPeriods++; - positionDays = positionPeriods * timeFrame / ONE_DAY_IN_MILISECONDS; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.positionPeriods = positionPeriods - } - } - } else { - positionPeriods = 0 - positionDays = 0 - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.positionPeriods = 0 - } - } - } - - /* Keeping Distance Counters Up-to-date */ - if ( - distanceToLast.triggerOn > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.triggerOn++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOn = distanceToLast.triggerOn - } - } - } - - if ( - distanceToLast.triggerOff > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.triggerOff++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - } - } - } - - if ( - distanceToLast.takePosition > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.takePosition++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.takePosition = distanceToLast.takePosition - } - } - } - - if ( - distanceToLast.closePosition > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.closePosition++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.takePosition = distanceToLast.closePosition - } - } - } - - /* Checking if Stop or Take Profit were hit */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') && - takePositionNow !== true - ) { - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - /* Checking what happened since the last execution. We need to know if the Stop Loss - or our Take Profit were hit. */ - - /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ - - if ((baseAsset === bot.market.baseAsset && candle.max >= stopLoss) || (baseAsset !== bot.market.baseAsset && candle.min <= stopLoss)) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Stop Loss was hit."); } - /* - Hit Point Validation - - This prevents misscalculations when a formula places the stop loss in this case way beyond the market price. - If we take the stop loss value at those situation would be a huge distortion of facts. - */ - - if (baseAsset === bot.market.baseAsset) { - if (stopLoss < candle.min) { - stopLoss = candle.min - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - } - } else { - if (stopLoss > candle.max) { - stopLoss = candle.max - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - } - } - - let slippedStopLoss = stopLoss - - /* Apply the Slippage */ - let slippageAmount = slippedStopLoss * bot.VALUES_TO_USE.slippage.stopLoss / 100 - - if (baseAsset === bot.market.baseAsset) { - slippedStopLoss = slippedStopLoss + slippageAmount - } else { - slippedStopLoss = slippedStopLoss - slippageAmount - } - - closeRate = slippedStopLoss; - - marketRate = closeRate; - type = '"Close@StopLoss"'; - strategyStage = 'Close Stage'; - checkAnnouncements(strategy.closeStage, 'Stop') - - stopLossStage = 'No Stage'; - takeProfitStage = 'No Stage'; - currentTrade.end = candle.end; - currentTrade.status = 1; - currentTrade.exitType = 1; - currentTrade.endRate = closeRate; - - closePositionNow = true; - addToSnapshots = true - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - yesterday.currentTrade.end = currentTrade.end; - yesterday.currentTrade.status = currentTrade.status; - yesterday.currentTrade.exitType = currentTrade.exitType; - yesterday.currentTrade.endRate = currentTrade.endRate; - } - } - } - - /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ - - if ((baseAsset === bot.market.baseAsset && candle.min <= takeProfit) || (baseAsset !== bot.market.baseAsset && candle.max >= takeProfit)) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Take Profit was hit."); } - /* - Hit Point Validation: - - This prevents misscalculations when a formula places the take profit in this case way beyond the market price. - If we take the stop loss value at those situation would be a huge distortion of facts. - */ - - if (baseAsset === bot.market.baseAsset) { - if (takeProfit > candle.max) { - takeProfit = candle.max - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - } - } else { - if (takeProfit < candle.min) { - takeProfit = candle.min - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - } - } - - let slippedTakeProfit = takeProfit - /* Apply the Slippage */ - let slippageAmount = slippedTakeProfit * bot.VALUES_TO_USE.slippage.takeProfit / 100 - - if (baseAsset === bot.market.baseAsset) { - slippedTakeProfit = slippedTakeProfit + slippageAmount - } else { - slippedTakeProfit = slippedTakeProfit - slippageAmount - } - - closeRate = slippedTakeProfit; - - marketRate = closeRate; - type = '"Close@TakeProfit"'; - strategyStage = 'Close Stage'; - checkAnnouncements(strategy.closeStage, 'Take Profit') - - stopLossStage = 'No Stage'; - takeProfitStage = 'No Stage'; - - currentTrade.end = candle.end; - currentTrade.status = 1; - currentTrade.exitType = 2; - currentTrade.endRate = closeRate; - - closePositionNow = true; - addToSnapshots = true - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - - yesterday.currentTrade.end = currentTrade.end; - yesterday.currentTrade.status = currentTrade.status; - yesterday.currentTrade.exitType = currentTrade.exitType; - yesterday.currentTrade.endRate = currentTrade.endRate; - } - } - } - } - - /* Taking a Position */ - if ( - takePositionNow === true - ) { - takePositionNow = false - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Entering code block."); } - - /* Inicializing this counter */ - distanceToLast.takePosition = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.takePosition = distanceToLast.takePosition - } - } - - /* Position size and rate */ - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - tradePositionSize = strategy.positionSize; - tradePositionRate = strategy.positionRate; - - /* We take what was calculated at the formula and apply the slippage. */ - let slippageAmount = tradePositionRate * bot.VALUES_TO_USE.slippage.positionRate / 100 - - if (baseAsset === bot.market.baseAsset) { - tradePositionRate = tradePositionRate - slippageAmount - } else { - tradePositionRate = tradePositionRate + slippageAmount - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.tradePositionSize = tradePositionSize; - yesterday.tradePositionRate = tradePositionRate; - } - } - - /* Update the trade record information. */ - currentTrade.begin = candle.begin; - currentTrade.beginRate = tradePositionRate; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentTrade.begin = currentTrade.begin; - yesterday.currentTrade.beginRate = currentTrade.beginRate; - } - } - - /* Check if we need to execute. */ - if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not.*/ - /* Check that we are in LIVE MODE */ - if (bot.startMode === "Live") { - /* We see if we need to put the actual order at the exchange. */ - if (interExecutionMemory.executionContext !== undefined) { - switch (interExecutionMemory.executionContext.status) { - case "Without a Position": { // We need to put the order because It was not put yet. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return - } - } - break - } - case "Position Closed": { // Waiting for a confirmation that the position was closed. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return - } - } - break - } - case "Taking Position": { // Waiting for a confirmation that the position was taken. - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is Taking Position."); } - break - } - case "In a Position": { // This should mean that we already put the order at the exchange. - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is In a Position."); } - break - } - } - } else { // The context does not exist so it means we are not in a position. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return - } - } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Not trading live."); } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Not the last closed candle."); } - } - - takePositionAtSimulation() - - return - - function putOpeningOrder() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function."); } - - /* We wont take a position unless we are withing the bot.VALUES_TO_USE.timeRange.initialDatetime and the bot.VALUES_TO_USE.timeRange.finalDatetime range */ - if (bot.VALUES_TO_USE.timeRange.initialDatetime !== undefined) { - if (candle.end < bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because current candle ends before the start date. -> bot.VALUES_TO_USE.timeRange.initialDatetime = " + bot.VALUES_TO_USE.timeRange.initialDatetime); } - takePositionAtSimulation() - return; - } - } - - /*We wont take a position if we are past the final datetime */ - if (bot.VALUES_TO_USE.timeRange.finalDatetime !== undefined) { - if (candle.begin > bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> putOpeningOrder -> Not placing the trade at the exchange because current candle begins after the end date. -> bot.VALUES_TO_USE.timeRange.finalDatetime = " + bot.VALUES_TO_USE.timeRange.finalDatetime); } - takePositionAtSimulation() - return; - } - } - - /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ - if (interExecutionMemory.executionContext !== undefined) { - if (interExecutionMemory.executionContext.periods !== undefined) { - if (periods <= interExecutionMemory.executionContext.periods) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because it was already placed at a previous execution."); } - takePositionAtSimulation() - return; - } - } - } - - /* We are not going to place orders based on outdated information. The next filter prevents firing orders when backtesting. */ - if (currentDay) { - let today = new Date(Math.trunc((new Date().valueOf()) / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - let processDay = new Date(Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - if (today.valueOf() !== processDay.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because the current candle belongs to the previous day and that is considered simulation and not live trading."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> today = " + today); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = " + processDay); } - takePositionAtSimulation() - return; - } - } - - let orderPrice - let amountA - let amountB - let orderSide - - - if (baseAsset === bot.market.baseAsset) { - orderSide = "sell" - - orderPrice = tradePositionRate - - amountA = tradePositionSize * orderPrice - amountB = tradePositionSize - - } else { - orderSide = "buy" - - orderPrice = tradePositionRate - - amountA = tradePositionSize - amountB = tradePositionSize / orderPrice - - } - - interExecutionMemory.executionContext = { - status: "Taking Position", - periods: periods, - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Ready to create order."); } - exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) - - function onOrderCreated(err, order) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function."); } - - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE "); } - interExecutionMemory.executionContext = { - status: "In a Position", - periods: periods, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - takePositionAtSimulation() - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE "); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = " + err.message); } - strategy.openStage.openExecution.error = err.message - afterLoop() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE "); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = " + err.message); } - strategy.openStage.openExecution.error = err.message - afterLoop() - return; - } - } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Unexpected Response -> Message = " + err.message); } - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } - } - - function takePositionAtSimulation() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionAtSimulation -> Entering function."); } - - /* Continue with the simulation */ - calculateTakeProfit(); - calculateStopLoss(); - - marketRate = candle.close; - - previousBalanceBaseAsset = balanceBaseAsset; - previousBalanceQuotedAsset = balanceQuotedAsset; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.previousBalanceBaseAsset = previousBalanceBaseAsset; - yesterday.previousBalanceQuotedAsset = previousBalanceQuotedAsset; - } - } - - lastTradeProfitLoss = 0; - lastTradeROI = 0; - - let feePaid = 0 - - if (baseAsset === bot.market.baseAsset) { - - feePaid = tradePositionSize * tradePositionRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceQuotedAsset = balanceQuotedAsset + tradePositionSize * tradePositionRate - feePaid; - balanceBaseAsset = balanceBaseAsset - tradePositionSize; - } else { - - feePaid = tradePositionSize / tradePositionRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceBaseAsset = balanceBaseAsset + tradePositionSize / tradePositionRate - feePaid; - balanceQuotedAsset = balanceQuotedAsset - tradePositionSize; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - yesterday.lastTradeProfitLoss = lastTradeProfitLoss; - yesterday.lastTradeROI = lastTradeROI; - } - } - - addRecord(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionAtSimulation -> Exiting Loop Body after taking position at simulation."); } - controlLoop(); - return - } - } - - if (closePositionNow === true) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Entering code block."); } - - closePositionNow = false - - /* Inicializing this counter */ - distanceToLast.closePosition = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.closePosition = distanceToLast.closePosition - } - } - - /* Position size and rate */ - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not.*/ - /* Check that we are in LIVE MODE */ - if (bot.startMode === "Live") { - /* We see if we need to put the actual order at the exchange. */ - if (interExecutionMemory.executionContext !== undefined) { - switch (interExecutionMemory.executionContext.status) { - case "Without a Position": { // No way to close anything at the exchange. - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Without a Position."); } - break - } - case "In a Position": { // This should mean that we already put the order at the exchange. - if (strategy.closeStage !== undefined) { - if (strategy.closeStage.closeExecution !== undefined) { - putClosingOrder() - return - } - } - break - } - case "Closing Position": { // Waiting for a confirmation that the position was taken. - if (strategy.closeStage !== undefined) { - if (strategy.closeStage.closeExecution !== undefined) { - putClosingOrder() - return - } - } - break - } - - case "Position Closed": { // - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed."); } - break - } - } - } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Not within the last 10 candles."); } - } - - closePositionAtSimulation() - return - - function putClosingOrder() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> putClosingOrder -> Entering function."); } - - /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ - if (interExecutionMemory.executionContext !== undefined) { - if (interExecutionMemory.executionContext.periods !== undefined) { - if (periods <= interExecutionMemory.executionContext.periods) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> Exiting function because this closing was already submited at a previous execution."); } - closePositionAtSimulation() - return; - } - } - } - - let orderPrice - let amountA - let amountB - let orderSide - - if (baseAsset === bot.market.baseAsset) { - orderSide = "buy" - - orderPrice = ticker.last + 100; // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. - - amountA = balanceQuotedAsset - amountB = balanceQuotedAsset / orderPrice - - } else { - orderSide = "sell" - - orderPrice = ticker.last - 100; // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. - - amountA = balanceBaseAsset * orderPrice - amountB = balanceBaseAsset - - } - - interExecutionMemory.executionContext = { - status: "Closing Position", - periods: periods, - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> About to close position at the exchange."); } - exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) - - function onOrderCreated(err, order) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function."); } - - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE "); } - interExecutionMemory.executionContext = { - status: "Position Closed", - periods: periods, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - closePositionAtSimulation() - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE "); } - /* We will assume that the problem is temporary, and expect that it will work at the next execution.*/ - strategy.closeStage.closeExecution.error = err.message - afterLoop() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE "); } - strategy.closeStage.closeExecution.error = err.message - afterLoop() - return; - } - } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Unexpected Response -> Message = " + err.message); } - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } - } - - function closePositionAtSimulation() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> closePositionAtSimulation -> Entering function."); } - - roundtrips++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.roundtrips = roundtrips - } - } - - let feePaid = 0 - - if (baseAsset === bot.market.baseAsset) { - strategy.positionSize = balanceQuotedAsset / closeRate; - strategy.positionRate = closeRate; - - feePaid = balanceQuotedAsset / closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceBaseAsset = balanceBaseAsset + balanceQuotedAsset / closeRate - feePaid; - balanceQuotedAsset = 0; - } else { - strategy.positionSize = balanceBaseAsset * closeRate; - strategy.positionRate = closeRate; - - feePaid = balanceBaseAsset * closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceQuotedAsset = balanceQuotedAsset + balanceBaseAsset * closeRate - feePaid; - balanceBaseAsset = 0; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - } - } - - if (baseAsset === bot.market.baseAsset) { - lastTradeProfitLoss = balanceBaseAsset - previousBalanceBaseAsset; - lastTradeROI = lastTradeProfitLoss * 100 / tradePositionSize; - if (isNaN(lastTradeROI)) { lastTradeROI = 0; } - profit = balanceBaseAsset - bot.VALUES_TO_USE.initialBalanceA; - } else { - lastTradeProfitLoss = balanceQuotedAsset - previousBalanceQuotedAsset; - lastTradeROI = lastTradeProfitLoss * 100 / tradePositionSize; - if (isNaN(lastTradeROI)) { lastTradeROI = 0; } - profit = balanceQuotedAsset - bot.VALUES_TO_USE.initialBalanceB; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.lastTradeProfitLoss = lastTradeProfitLoss; - yesterday.profit = profit; - yesterday.lastTradeROI = lastTradeROI; - } - } - - currentTrade.lastTradeROI = lastTradeROI; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentTrade.lastTradeROI = currentTrade.lastTradeROI; - } - } - - if (lastTradeProfitLoss > 0) { - hits++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.hits = hits - } - } - - } else { - fails++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.fails = fails - } - } - } - - if (baseAsset === bot.market.baseAsset) { - ROI = (bot.VALUES_TO_USE.initialBalanceA + profit) / bot.VALUES_TO_USE.initialBalanceA - 1; - hitRatio = hits / roundtrips; - anualizedRateOfReturn = ROI / days * 365; - } else { - ROI = (bot.VALUES_TO_USE.initialBalanceB + profit) / bot.VALUES_TO_USE.initialBalanceB - 1; - hitRatio = hits / roundtrips; - anualizedRateOfReturn = ROI / days * 365; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.ROI = ROI; - yesterday.hitRatio = hitRatio; - yesterday.anualizedRateOfReturn = anualizedRateOfReturn; - } - } - - addRecord(); - - stopLoss = 0; - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - takeProfit = 0; - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - - tradePositionRate = 0; - tradePositionSize = 0; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.tradePositionSize = tradePositionSize; - yesterday.tradePositionRate = tradePositionRate; - } - } - - timerToCloseStage = candle.begin - stopLossStage = 'No Stage'; - takeProfitStage = 'No Stage'; - stopLossPhase = -1; - takeProfitPhase = -1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - yesterday.stopLossPhase = stopLossPhase; - yesterday.takeProfitPhase = takeProfitPhase; - } - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> closePositionAtSimulation -> Exiting Loop Body after closing position at simulation."); } - controlLoop(); - return - } - } - - /* Closing the Closing Stage */ - if (strategyStage === 'Close Stage') { - if (candle.begin - 5 * 60 * 1000 > timerToCloseStage) { - - currentStrategy.number = currentStrategyIndex - currentStrategy.end = candle.end; - currentStrategy.endRate = candle.min; - currentStrategy.status = 1; // This means the strategy is closed, i.e. that has a begin and end. - - if (processingDailyFiles) { - if (positionedAtYesterday) { - - yesterday.currentStrategy.number = currentStrategy.number; - yesterday.currentStrategy.end = currentStrategy.end; - yesterday.currentStrategy.endRate = currentStrategy.endRate; - yesterday.currentStrategy.status = currentStrategy.status; - } - } - - currentStrategyIndex = -1; - strategyStage = 'No Stage'; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentStrategyIndex = currentStrategyIndex; - yesterday.strategyStage = strategyStage; - } - } - - timerToCloseStage = 0 - - distanceToLast.triggerOff = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - } - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing the Closing Stage -> Exiting Close Stage."); } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing the Closing Stage -> Waiting for timer."); } - } - } - - /* Not a buy or sell condition */ - - marketRate = candle.close; - addRecord(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Exiting Loop Body after adding a record."); } - controlLoop(); - return - - function addRecord() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> addRecord -> Entering function."); } - - // Since we are going to write the message to a file that the Simulation Executor is going to read, we use the abbreviations. - let messageType; - let message; - let simulationRecord; - let executionRecord; - - let strategyStageNumber - switch (strategyStage) { - case 'No Stage': { - strategyStageNumber = 0 - break - } - case 'Trigger Stage': { - strategyStageNumber = 1 - break - } - case 'Open Stage': { - strategyStageNumber = 2 - break - } - case 'Manage Stage': { - strategyStageNumber = 3 - break - } - case 'Close Stage': { - strategyStageNumber = 4 - break - } - } - - if (balanceBaseAsset === Infinity) { - balanceBaseAsset = Number.MAX_SAFE_INTEGER - } - - if (balanceQuotedAsset === Infinity) { - balanceQuotedAsset = Number.MAX_SAFE_INTEGER - } - - let quotedBaseAsset = '"' + baseAsset + '"' - let quotedQuotedAsset = '"' + quotedAsset + '"' - - simulationRecord = { - begin: candle.begin, - end: candle.end, - type: type, - marketRate: marketRate, - amount: 1, - balanceA: balanceBaseAsset, - balanceB: balanceQuotedAsset, - profit: profit, - lastTradeProfitLoss: lastTradeProfitLoss, - stopLoss: stopLoss, - roundtrips: roundtrips, - hits: hits, - fails: fails, - hitRatio: hitRatio, - ROI: ROI, - periods: periods, - days: days, - anualizedRateOfReturn: anualizedRateOfReturn, - positionRate: tradePositionRate, - lastTradeROI: lastTradeROI, - strategy: currentStrategyIndex, - strategyStageNumber: strategyStageNumber, - takeProfit: takeProfit, - stopLossPhase: stopLossPhase, - takeProfitPhase: takeProfitPhase, - executionRecord: '', - positionSize: tradePositionSize, - initialBalanceA: bot.VALUES_TO_USE.initialBalanceA, - minimumBalanceA: bot.VALUES_TO_USE.minimumBalanceA, - maximumBalanceA: bot.VALUES_TO_USE.maximumBalanceA, - initialBalanceB: bot.VALUES_TO_USE.initialBalanceB, - minimumBalanceB: bot.VALUES_TO_USE.minimumBalanceB, - maximumBalanceB: bot.VALUES_TO_USE.maximumBalanceB, - baseAsset: quotedBaseAsset, - quotedAsset: quotedQuotedAsset, - marketBaseAsset: '"' + bot.market.baseAsset + '"', - marketQuotedAsset: '"' + bot.market.quotedAsset + '"', - positionPeriods: positionPeriods, - positionDays: positionDays, - distanceToLastTriggerOn: distanceToLast.triggerOn, - distanceToLastTriggerOff: distanceToLast.triggerOff, - distanceToLastTakePosition: distanceToLast.takePosition, - distanceToLastClosePosition: distanceToLast.closePosition - } - - recordsArray.push(simulationRecord); - - type = '""'; - - /* Prepare the information for the Conditions File */ - - conditionsArrayRecord.push(currentStrategyIndex); - conditionsArrayRecord.push(strategyStageNumber); - conditionsArrayRecord.push(stopLossPhase); - conditionsArrayRecord.push(takeProfitPhase); - conditionsArrayRecord.push(conditionsArrayValues); - conditionsArrayRecord.push(formulasErrors); - conditionsArrayRecord.push(formulasValues); - - conditionsArray.push(conditionsArrayRecord); - - /* - Lets see if there will be an open strategy ... - Except if we are at the head of the market (remember we skipped the last candle for not being closed.) - */ - if (currentStrategy.begin !== 0 && currentStrategy.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { - currentStrategy.status = 2; // This means the strategy is open, i.e. that has a begin but no end. - currentStrategy.end = candle.end - } - - /* Prepare the information for the Strategies File*/ - if (currentStrategy.begin !== 0 && currentStrategy.end !== 0) { - strategiesArray.push(currentStrategy); - - currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '', - name: '' - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '', - name: '' - } - } - } - } - - /* - Lets see if there will be an open trade ... - Except if we are at the head of the market (remember we skipped the last candle for not being closed.) - */ - if (currentTrade.begin !== 0 && currentTrade.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { - currentTrade.status = 2; // This means the trade is open - currentTrade.end = candle.end - currentTrade.endRate = candle.close - - /* Here we will calculate the ongoing ROI */ - if (baseAsset === bot.market.baseAsset) { - currentTrade.lastTradeROI = (tradePositionRate - candle.close) / tradePositionRate * 100 - } else { - currentTrade.lastTradeROI = (candle.close - tradePositionRate) / tradePositionRate * 100 - } - } - - /* Snapshots Management (before we generate the trade record and delete that info) */ - if (saveAsLastTriggerOnSnapshot === true) { - snapshots.lastTriggerOn = snapshotDataRecord - saveAsLastTriggerOnSnapshot = false - } - - if (saveAsLastTakePositionSnapshot === true) { - snapshots.lastTakePosition = snapshotDataRecord - saveAsLastTakePositionSnapshot = false - } - - if (addToSnapshots === true) { - let closeValues = [ - roundtrips, // Trade Number - (new Date(candle.begin)).toISOString(), // Datetime - tradingSystem.strategies[currentStrategyIndex].name, // Strategy - currentStrategy.triggerOnSituation, // Trigger On Situation - currentTrade.takePositionSituation, // Take Position Situation - hitOrFial(), // Result - lastTradeROI, // ROI - simulationRecord.type.replace(/"/g, '') // Exit Type - ] - - function hitOrFial() { - if (lastTradeROI > 0) { return 'HIT' } else { return 'FAIL' } - } - - if (positionedAtYesterday === false) { - snapshots.triggerOn.push(closeValues.concat(snapshots.lastTriggerOn)) - snapshots.takePosition.push(closeValues.concat(snapshots.lastTakePosition)) - } - snapshots.lastTriggerOn = undefined - snapshots.lastTakePosition = undefined - addToSnapshots = false - } - - let closeHeaders = ['Trade Number', 'Close Datetime', 'Strategy', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type'] - if (snapshots.headers === undefined) { - snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshotLoopHeaders))) - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.snapshots.headers = snapshots.headers; - yesterday.snapshots.triggerOn = snapshots.triggerOn; - yesterday.snapshots.takePosition = snapshots.takePosition; - yesterday.snapshots.lastTriggerOn = snapshots.lastTriggerOn; - yesterday.snapshots.lastTakePosition = snapshots.lastTakePosition; - } - } - - /* Prepare the information for the Trades File */ - if (currentTrade.begin !== 0 && currentTrade.end !== 0) { - - currentTrade.profit = lastTradeProfitLoss; - - tradesArray.push(currentTrade); - - currentTrade = { - begin: 0, - end: 0, - status: 0, - lastTradeROI: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentTrade = { - begin: 0, - end: 0, - status: 0, - lastTradeROI: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - } - } - } - - /* After everything at the simulation level was done, we will do the annoucements that are pending.*/ - makeAnnoucements() - } - - function checkAnnouncements(node, value) { - /* - Value is an optional parameter that represents the value that the announcement is monitoring for change (for numeric values only). - If we do receive this value, we will only make the annoucement if the variance is grater than the user pre-defined value - for this variance. - */ - - if (node.announcements !== undefined) { - for (let i = 0; i < node.announcements.length; i++) { - let announcement = node.announcements[i] - let key = node.type + "-" + announcement.name + "-" + announcement.id - - let lastPeriodAnnounced = -1 - let newAnnouncementRecord = {} - - for (let j = 0; j < interExecutionMemory.announcements.length; j++) { - let announcementRecord = interExecutionMemory.announcements[j] - if (announcementRecord.key === key) { - lastPeriodAnnounced = announcementRecord.periods - newAnnouncementRecord = announcementRecord - break - } - } - - if (periods > lastPeriodAnnounced) { - - if (isNaN(value) === false) { - /* The Value Variation is what tells us how much the value already announced must change in order to annouce it again. */ - let valueVariation - - let code = announcement.code - valueVariation = code.valueVariation - - if (newAnnouncementRecord.value !== undefined && valueVariation !== undefined) { - let upperLimit = newAnnouncementRecord.value + newAnnouncementRecord.value * valueVariation / 100 - let lowerLimit = newAnnouncementRecord.value - newAnnouncementRecord.value * valueVariation / 100 - if (value > lowerLimit && value < upperLimit) { - /* There is not enough variation to announce this again. */ - return - } - } - } - - /* - We store the announcement temporarily at an Array to differ its execution, becasue we need to evaulate its formula - and at this point in time the potential variables used at the formula are still not set. - */ - announcement.value = value - announcementsToBeMade.push(announcement) - - /* Next, we will remmeber this announcement was already done, so that it is not announced again in further processing of the same day. */ - if (newAnnouncementRecord.periods !== undefined) { - newAnnouncementRecord.periods = periods - newAnnouncementRecord.value = value - } else { - newAnnouncementRecord = { - key: key, - periods: periods, - value: value - } - interExecutionMemory.announcements.push(newAnnouncementRecord) - } - } - } - } - } - - function makeAnnoucements() { - /* Here we go through all the annoucements that need to be done during this loop, and we just do them. */ - for (let i = 0; i < announcementsToBeMade.length; i++) { - announcement = announcementsToBeMade[i] - /* Here we check if there is a formula attached to the annoucement, we evaluate it to get the annoucement text. */ - let formulaValue - if (announcement.formula !== undefined) { - try { - let value = announcement.value - formulaValue = eval(announcement.formula.code); - } catch (err) { - announcement.formula.error = err.message - } - } - announcement.formulaValue = formulaValue - - if (bot.SESSION.socialBots !== undefined) { - bot.SESSION.socialBots.announce(announcement) - } - } - } - - 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, '') - 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."); } - - /* Checking if we should continue processing this loop or not.*/ - if (bot.STOP_SESSION === true) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session."); } - console.log("[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.") - afterLoop() - return - } - - if (global.STOP_TASK_GRACEFULLY === true) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task."); } - console.log("[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.") - afterLoop() - return - } - - currentCandleIndex++ - if (currentCandleIndex < candles.length) { - setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. - } else { - afterLoop() - } - } - - function afterLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> afterLoop -> Entering function."); } - - /* - Before returning we need to see if we have to record some of our counters at the interExecutionMemory. - To do that, the condition to be met is that this execution must include all candles of the current day. - */ - - if (processingDailyFiles) { - - if (lastCandle.end === lastInstantOfTheDay) { - - interExecutionMemory.snapshots = { - headers: yesterday.snapshots.headers, - triggerOn: yesterday.snapshots.triggerOn, - takePosition: yesterday.snapshots.takePosition, - lastTriggerOn: yesterday.snapshots.lastTriggerOn, - lastTakePosition: yesterday.snapshots.lastTakePosition - } - - interExecutionMemory.stopLoss = yesterday.stopLoss - interExecutionMemory.takeProfit = yesterday.takeProfit - - interExecutionMemory.previousBalanceBaseAsset = yesterday.previousBalanceBaseAsset - interExecutionMemory.previousBalanceQuotedAsset = yesterday.previousBalanceQuotedAsset - - interExecutionMemory.tradePositionRate = yesterday.tradePositionRate - interExecutionMemory.tradePositionSize = yesterday.tradePositionSize - - interExecutionMemory.currentStrategyIndex = yesterday.currentStrategyIndex - interExecutionMemory.strategyStage = yesterday.strategyStage - - interExecutionMemory.stopLossPhase = yesterday.stopLossPhase - interExecutionMemory.stopLossStage = yesterday.stopLossStage - - interExecutionMemory.takeProfitPhase = yesterday.takeProfitPhase - interExecutionMemory.takeProfitStage = yesterday.takeProfitStage - - interExecutionMemory.currentStrategy = { - begin: yesterday.currentStrategy.begin, - end: yesterday.currentStrategy.end, - status: yesterday.currentStrategy.status, - number: yesterday.currentStrategy.number, - beginRate: yesterday.currentStrategy.beginRate, - endRate: yesterday.currentStrategy.endRate, - triggerOnSituation: yesterday.currentStrategy.triggerOnSituation, - name: yesterday.currentStrategy.name - } - - interExecutionMemory.currentTrade = { - begin: yesterday.currentTrade.begin, - end: yesterday.currentTrade.end, - status: yesterday.currentTrade.status, - profit: yesterday.currentTrade.profit, - exitType: yesterday.currentTrade.exitType, - beginRate: yesterday.currentTrade.beginRate, - endRate: yesterday.currentTrade.endRate, - takePositionSituation: yesterday.currentTrade.takePositionSituation - } - - interExecutionMemory.balanceBaseAsset = yesterday.balanceBaseAsset; - interExecutionMemory.balanceQuotedAsset = yesterday.balanceQuotedAsset; - interExecutionMemory.lastTradeProfitLoss = yesterday.lastTradeProfitLoss; - interExecutionMemory.profit = yesterday.profit; - interExecutionMemory.lastTradeROI = yesterday.lastTradeROI; - - interExecutionMemory.roundtrips = yesterday.roundtrips; - interExecutionMemory.fails = yesterday.fails; - interExecutionMemory.hits = yesterday.hits; - interExecutionMemory.periods = yesterday.periods; - interExecutionMemory.positionPeriods = yesterday.positionPeriods; - - interExecutionMemory.distanceToLast.triggerOn = yesterday.distanceToLast.triggerOn - interExecutionMemory.distanceToLast.triggerOff = yesterday.distanceToLast.triggerOff - interExecutionMemory.distanceToLast.takePosition = yesterday.distanceToLast.takePosition - interExecutionMemory.distanceToLast.closePosition = yesterday.distanceToLast.closePosition - - interExecutionMemory.messageId = interExecutionMemory.messageId + yesterday.messageId; - interExecutionMemory.orderId = interExecutionMemory.orderId + yesterday.orderId; - - interExecutionMemory.hitRatio = yesterday.hitRatio; - interExecutionMemory.ROI = yesterday.ROI; - interExecutionMemory.anualizedRateOfReturn = yesterday.anualizedRateOfReturn; - } - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> callback -> recordsArray.length = " + recordsArray.length); } - - callback(tradingSystem, recordsArray, conditionsArray, strategiesArray, tradesArray, snapshots.headers, snapshots.triggerOn, snapshots.takePosition); - } - - function getElement(pArray, currentCandle, datasetName) { - if (pArray === undefined) { return } - try { - let element; - for (let i = 0; i < pArray.length; i++) { - element = pArray[i]; - - if (currentCandle.end === element.end) { // when there is an exact match at the end we take that element - return element - } else { - if ( - i > 0 && - element.end > currentCandle.end - ) { - let previousElement = pArray[i - 1] - if (previousElement.end < currentCandle.end) { - return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element. - } else { - return - } - } - } - } - return - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> getElement -> datasetName = " + datasetName); - logger.write(MODULE_NAME, "[ERROR] runSimulation -> getElement -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - - catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } -}; - - - diff --git a/TaskServer/server.js b/TaskServer/server.js index 80ac46a62..56f923cae 100644 --- a/TaskServer/server.js +++ b/TaskServer/server.js @@ -25,7 +25,11 @@ global.CUSTOM_FAIL_RESPONSE = { message: "Custom Message" }; -const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; +global.ROOT_DIR = './'; + +global.ONE_YEAR_IN_MILISECONDS = 365 * 24 * 60 * 60 * 1000 +global.ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000 +global.ONE_MIN_IN_MILISECONDS = 60 * 1000 global.LOGGER_MAP = new Map() global.SESSION_MAP = new Map() @@ -57,7 +61,7 @@ function finalizeSessions() { } } -process.on('exit', function (code) { +process.on('exit', function (config) { if (global.TASK_NODE !== undefined) { /* We send an event signaling that the Task is being terminated. */ @@ -68,7 +72,7 @@ process.on('exit', function (code) { global.EVENT_SERVER_CLIENT = undefined } - //console.log('[INFO] Task Server -> server -> process.on.exit -> About to exit -> code = ' + code) + //console.log('[INFO] Task Server -> server -> process.on.exit -> About to exit -> config = ' + config) }) /* Here we listen for the message to stop this Task / Process comming from the Task Manager, which is the paret of this node js process. */ @@ -84,13 +88,17 @@ process.on('message', message => { let key = global.TASK_NODE.name + '-' + global.TASK_NODE.type + '-' + global.TASK_NODE.id console.log('[INFO] Task Server -> server -> process.on -> Stopping Task ' + key + '. Nodejs process will be exited in less than 1 minute.') setTimeout(global.EXIT_NODE_PROCESS, 60000); - } + } }); let shuttingDownProcess = false global.EXIT_NODE_PROCESS = function exitProcess() { + if (global.unexpectedError !== undefined) { + global.taskError(undefined, "An unexpected error caused the Task to stop.") + } + if (shuttingDownProcess === true) { return } shuttingDownProcess = true @@ -101,7 +109,7 @@ global.EXIT_NODE_PROCESS = function exitProcess() { clearInterval(global.HEARTBEAT_INTERVAL_HANDLER) for (let i = 0; i < global.TASK_NODE.bot.processes.length; i++) { - let code = global.TASK_NODE.bot.processes[i].code + let config = global.TASK_NODE.bot.processes[i].config let process = global.TASK_NODE.bot.processes[i] key = process.name + '-' + process.type + '-' + process.id @@ -138,7 +146,7 @@ let taskId = process.argv[2] // reading what comes as an argument of the nodejs /* Setting up the global Event Handler */ let EVENT_SERVER_CLIENT = require('./EventServerClient.js'); - + global.EVENT_SERVER_CLIENT = EVENT_SERVER_CLIENT.newEventsServerClient() global.EVENT_SERVER_CLIENT.initialize(preLoader) global.STOP_TASK_GRACEFULLY = false; @@ -162,7 +170,7 @@ function preLoader() { } } else { /* This process was started not by the Task Manager, but independently (most likely for debugging purposes). In this case we listen to an event with the Task Info that should be emitted at the UI */ - try { + try { //console.log('[INFO] Task Server -> server -> preLoader -> Waiting for event to start debugging...') global.EVENT_SERVER_CLIENT.listenToEvent('Task Server', 'Debug Task Started', undefined, 'Task Server', undefined, startDebugging) function startDebugging(message) { @@ -190,15 +198,33 @@ function bootLoader() { function taskHearBeat() { - /* The heartbeat event is raised at the event handler of the instance of this task, created at the UI. */ + /* The heartbeat event is raised at the event handler of the instance of this task, created at the UI. */ let event = { seconds: (new Date()).getSeconds() } - global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Heartbeat', event) + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Heartbeat', event) + } + + global.taskError = function taskError(node, errorMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + errorMessage: errorMessage + } + } else { + event = { + errorMessage: errorMessage + } + } + + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Error', event) } for (let processIndex = 0; processIndex < global.TASK_NODE.bot.processes.length; processIndex++) { - let code = global.TASK_NODE.bot.processes[processIndex].code + let config = global.TASK_NODE.bot.processes[processIndex].config /* Validate that the minimun amount of input required are defined. */ @@ -257,17 +283,17 @@ function bootLoader() { continue } - if (global.TASK_NODE.bot.processes[processIndex].referenceParent.code.codeName === undefined) { + if (global.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName === undefined) { console.log("[WARN] Task Server -> server -> bootLoader -> Process Definition without a codeName defined. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent)); continue } - if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.code.codeName === undefined) { + if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.config.codeName === undefined) { console.log("[WARN] Task Server -> server -> bootLoader -> Bot Definition without a codeName defined. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode)); continue } - if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName === undefined) { + if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName === undefined) { console.log("[WARN] Task Server -> server -> bootLoader -> Data Mine without a codeName defined. -> Data Mine Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode)); continue } @@ -278,7 +304,7 @@ function bootLoader() { function startRoot(processIndex) { - // console.log('[INFO] Task Server -> server -> startRoot -> Entering function. ') + // console.log('[INFO] Task Server -> server -> startRoot -> Entering function. ') const ROOT_MODULE = require('./Root') let root = ROOT_MODULE.newRoot() @@ -292,20 +318,20 @@ function startRoot(processIndex) { } global.getPercentage = function (fromDate, currentDate, lastDate) { - let fromDays = Math.trunc(fromDate.valueOf() / ONE_DAY_IN_MILISECONDS) - let currentDays = Math.trunc(currentDate.valueOf() / ONE_DAY_IN_MILISECONDS) - let lastDays = Math.trunc(lastDate.valueOf() / ONE_DAY_IN_MILISECONDS) + let fromDays = Math.trunc(fromDate.valueOf() / global.ONE_DAY_IN_MILISECONDS) + let currentDays = Math.trunc(currentDate.valueOf() / global.ONE_DAY_IN_MILISECONDS) + let lastDays = Math.trunc(lastDate.valueOf() / global.ONE_DAY_IN_MILISECONDS) let percentage = (currentDays - fromDays) * 100 / (lastDays - fromDays) if ((lastDays - fromDays) === 0) { percentage = 100 } - return percentage + return percentage } global.areEqualDates = function (date1, date2) { - let day1Days = Math.trunc(date1.valueOf() / ONE_DAY_IN_MILISECONDS) - let day2Days = Math.trunc(date2.valueOf() / ONE_DAY_IN_MILISECONDS) - + let day1Days = Math.trunc(date1.valueOf() / global.ONE_DAY_IN_MILISECONDS) + let day2Days = Math.trunc(date2.valueOf() / global.ONE_DAY_IN_MILISECONDS) + if (day1Days === day2Days) { return true } else { diff --git a/Trading-Systems/Sparta-WHB-BTC-USDT.json b/Trading-Systems/Sparta-WHB-BTC-USDT.json index 9933ddc6f..fa921dcef 100644 --- a/Trading-Systems/Sparta-WHB-BTC-USDT.json +++ b/Trading-Systems/Sparta-WHB-BTC-USDT.json @@ -1 +1 @@ -{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","code":"{ \n\"codeName\": \"Sparta-WHB-BTC-USDT\"\n}","strategies":[{"type":"Strategy","name":"Steep","triggerStage":{"type":"Trigger Stage","triggerOn":{"type":"Trigger On Event","situations":[{"type":"Situation","name":"Steep","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"04630f48-d984-4e80-ae51-614c9b8d8d4a","savedPayload":{"position":{"x":38654.42937601845,"y":24118.7146096835},"targetPosition":{"x":38896.03793282062,"y":23893.50644952296},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"72e53092-78e4-431d-bc2d-7f3079881067","savedPayload":{"position":{"x":38896.03793282062,"y":23893.50644952296},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"874e7f12-9a9f-4d01-9b5a-8d45b3244707","savedPayload":{"position":{"x":38467.02147676738,"y":23830.21107095448},"targetPosition":{"x":38771.169126085806,"y":23701.239147931672},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"639a3d0f-089c-4a72-bb8a-646a088c827f","savedPayload":{"position":{"x":38771.169126085806,"y":23701.239147931672},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"c2044baf-6f85-4b83-9984-efb4e163da43","savedPayload":{"position":{"x":38389.664496650454,"y":23495.04507117375},"targetPosition":{"x":38719.629522672716,"y":23477.904752152226},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"a182ddc2-c81f-4187-a683-e1eb70d2a073","savedPayload":{"position":{"x":38719.629522672716,"y":23477.904752152226},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation > 200","id":"386c8342-029f-43da-934f-842a63abc4cd","savedPayload":{"position":{"x":38431.586955243925,"y":23154.12292223018},"targetPosition":{"x":38747.56097033524,"y":23250.663790011644},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"40bc2e8f-a913-47d3-aedb-6d48c0ba53fa","savedPayload":{"position":{"x":38747.56097033524,"y":23250.663790011644},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"11ac851b-1b7f-4fd2-99ba-e5b374843f75","savedPayload":{"position":{"x":38587.837841833374,"y":22847.685517901587},"targetPosition":{"x":38851.72572661361,"y":23046.50017712293},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"ae77e599-0666-4ee2-9ba1-5454122da57e","savedPayload":{"position":{"x":38851.59129412593,"y":23046.46870027089},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"ce8ed2e0-78f2-4dc5-b67f-7cc0faa6f72d","savedPayload":{"position":{"x":38839.382799047715,"y":22613.10431827154},"targetPosition":{"x":39019.32925227039,"y":22890.193356793647},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"9bd67f05-1e80-4af4-8b3b-6326aea61169","savedPayload":{"position":{"x":39019.20836123038,"y":22890.163662927203},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"5607e471-72db-4adf-b542-5627836b2fc4","savedPayload":{"position":{"x":39156.005778308325,"y":22478.707014444873},"targetPosition":{"x":39230.30291243001,"y":22800.64350413514},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"668f372c-b50d-4ad7-bfe1-cb3594d0b1c9","savedPayload":{"position":{"x":39230.18484547141,"y":22800.612491027987},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope != \"Extreme\" || (chart.at01hs.bollingerSubChannel.period <=6 && chart.at08hs.bollingerChannel.direction === \"Down\" && chart.at06hs.bollingerChannel.direction === \"Down\" && chart.at04hs.bollingerChannel.direction === \"Down\" && chart.at02hs.bollingerSubChannel.slope === (\"Extreme\" || \"Steep\"))","id":"754d3dcd-a660-4a60-b553-60d4e4828fc2","savedPayload":{"position":{"x":39499.05202984986,"y":22460.737831992745},"targetPosition":{"x":39458.97682702611,"y":22788.66468167322},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"d54ee343-d311-4d85-a97f-52b1f68af31c","savedPayload":{"position":{"x":39458.86345462394,"y":22788.633396789402},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"d36638b7-6ffc-4dd8-a0aa-581dc203a3fd","savedPayload":{"position":{"x":39378.570631146256,"y":23443.53261737075},"targetPosition":{"x":39967.429056464694,"y":23743.542886573094},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"eb5ec9e9-b915-41db-917b-b3c4084fa2d0","savedPayload":{"position":{"x":39967.429056464694,"y":23743.542886573094},"targetPosition":{"x":40556.1702605225,"y":24043.408960214012},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"triggerOff":{"type":"Trigger Off Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close > chart.at01hs.bollingerBand.movingAverage","id":"04223c33-3fb5-495e-b481-f3f89d19772d","savedPayload":{"position":{"x":38210.648710788475,"y":24781.418713158193},"targetPosition":{"x":38647.010106152025,"y":24587.43993400261},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"06e77d55-6aff-46fb-940d-673fe9c79bd8","savedPayload":{"position":{"x":38647.010106152025,"y":24587.43993400261},"targetPosition":{"x":39083.52651184902,"y":24393.503671421502},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"8ccdd8c1-1ca7-4746-b504-16ca7dca9164","savedPayload":{"position":{"x":40147.90856068426,"y":22786.295224985486},"targetPosition":{"x":40352.049091964946,"y":23414.93778484698},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"1b2fd09f-1095-4513-b6cd-18f565a96fd2","savedPayload":{"position":{"x":40352.049091964946,"y":23414.93778484698},"targetPosition":{"x":40556.1702605225,"y":24043.408960214012},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takePosition":{"type":"Take Position Event","situations":[{"type":"Situation","name":"Steep","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"f72bf18a-b497-46d4-8be0-e327da01a533","savedPayload":{"position":{"x":39240.350911186295,"y":23504.494295275093},"targetPosition":{"x":39478.87025480116,"y":23522.725227223866},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"8c26040a-2bc1-4227-bede-1970dd8a7d74","savedPayload":{"position":{"x":39478.87025480116,"y":23522.725227223866},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"9fdb21ec-7e23-48f2-bcd8-448147ec07e1","savedPayload":{"position":{"x":39284.59696457764,"y":23305.152539451356},"targetPosition":{"x":39508.373518788445,"y":23389.868800061806},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"9d362d79-a971-4ca6-99db-733d52a345b2","savedPayload":{"position":{"x":39508.373518788445,"y":23389.868800061806},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"6c812d60-985b-402b-bd39-adf518b44d7a","savedPayload":{"position":{"x":39383.2217853877,"y":23126.478191715356},"targetPosition":{"x":39574.09296654021,"y":23270.811905752722},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"b62b951d-9f1d-4ef8-8a1b-8ec6ee301e5e","savedPayload":{"position":{"x":39574.09296654021,"y":23270.811905752722},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation > 200","id":"3c1b1284-cc15-44a0-ad56-152c608873c8","savedPayload":{"position":{"x":39528.19260345359,"y":22982.85266277603},"targetPosition":{"x":39670.68822663726,"y":23175.11183531663},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"d6a25acf-936d-4839-b8ed-aa1f1c26f283","savedPayload":{"position":{"x":39670.68822663726,"y":23175.11183531663},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"a9d0ee08-149f-4e02-b913-e31c6a81143c","savedPayload":{"position":{"x":39707.742010433845,"y":22885.903680709336},"targetPosition":{"x":39790.32484971696,"y":23110.517001753822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"a1786da7-a63b-4930-a9ac-bde381e03456","savedPayload":{"position":{"x":39790.32484971696,"y":23110.517001753822},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"03ea81ae-6b27-46e6-8355-98418b3102eb","savedPayload":{"position":{"x":39907.31271552981,"y":22843.480362937433},"targetPosition":{"x":39923.30854888421,"y":23082.253933862172},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"7293789d-7e2c-4418-91ff-1773f8300504","savedPayload":{"position":{"x":39923.30854888421,"y":23082.253933862172},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"1cbf188a-3e7e-486b-97f7-591e2942d4c2","savedPayload":{"position":{"x":40110.33565939289,"y":22859.030126104986},"targetPosition":{"x":40058.66797686181,"y":23092.616084393856},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"82ef75a8-d740-4a20-b382-f8a38b67b37f","savedPayload":{"position":{"x":40058.66797686181,"y":23092.616084393856},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope != \"Extreme\" || (chart.at01hs.bollingerSubChannel.period <=6 && chart.at08hs.bollingerChannel.direction === \"Down\" && chart.at06hs.bollingerChannel.direction === \"Down\" && chart.at04hs.bollingerChannel.direction === \"Down\" && chart.at02hs.bollingerSubChannel.slope === (\"Extreme\" || \"Steep\"))","id":"390add95-1bf1-4d10-9ce6-6f0adfdeb11d","savedPayload":{"position":{"x":40301.106300854844,"y":22931.31532649044},"targetPosition":{"x":40185.81529016951,"y":23140.7748187153},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"bad14acd-fac1-47a9-bb8c-35e3fba8017b","savedPayload":{"position":{"x":40185.81529016951,"y":23140.7748187153},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at08hs.bollingerBand.deviation < 1575","id":"5916a300-bd02-4d33-bb25-8132bbab6402","savedPayload":{"position":{"x":40463.76971325339,"y":23054.494110751715},"targetPosition":{"x":40294.25413728789,"y":23222.84607488945},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"70c87419-dd46-48a9-83df-6394dcdc9f81","savedPayload":{"position":{"x":40294.25413728789,"y":23222.84607488945},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at24hs.candle.open < chart.at24hs.bollingerBand.movingAverage + chart.at24hs.bollingerBand.deviation || chart.at24hs.candle.close < chart.at24hs.bollingerBand.movingAverage + chart.at24hs.bollingerBand.deviation","id":"485bc3a9-85ce-42a2-8bc8-3fd8c199a153","savedPayload":{"position":{"x":40559.83786637926,"y":23203.596114575474},"targetPosition":{"x":40375.24653894792,"y":23332.286513695442},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"1dc2cb20-f252-41ac-9617-0ae1115d3883","savedPayload":{"position":{"x":40375.24653894792,"y":23332.286513695442},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a77b35ef-82dd-4a0b-858a-a9332e93ffcd","savedPayload":{"position":{"x":40993.44114336273,"y":22808.180948915793},"targetPosition":{"x":40856.13716335811,"y":23454.689439619513},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[{"type":"Announcement","name":"Take Position","code":"{ \n\"text\": \"Take Position Event.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB took position at $\" + chart.at01hs.candle.close.toFixed(0) + \"!\"","id":"d5a86e8e-555a-4362-9d2f-d5bdf8a4e637","savedPayload":{"position":{"x":41298.2303357878,"y":22964.61172046955},"targetPosition":{"x":41077.186099344915,"y":23209.857438088973},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"207f6150-3902-445b-86c7-1539739421a5","savedPayload":{"position":{"x":41077.15627448108,"y":23209.336531641686},"targetPosition":{"x":40856.13716335811,"y":23454.689439619513},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"ac645fd1-0e1d-4b12-aaf1-3b8e081e847f","savedPayload":{"position":{"x":40856.13716335811,"y":23454.689439619513},"targetPosition":{"x":40556.1702605225,"y":24043.408960214012},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[],"id":"e4de9514-302c-44e1-a9a6-419e43d5e56f","savedPayload":{"position":{"x":40556.1702605225,"y":24043.408960214012},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"openStage":{"type":"Open Stage","initialDefinition":{"type":"Initial Definition","stopLoss":{"type":"Initial Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (positionRate + positionRate * 3 / 100 > 150) {positionRate + positionRate * 3 / 100} else {150}","id":"90875495-204f-47d8-b90a-e740b494d1aa","savedPayload":{"position":{"x":41543.57064293352,"y":22327.617504659756},"targetPosition":{"x":42021.77913634585,"y":22455.81161228222},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.previous.close < positionRate - positionRate * 5 / 100","id":"f0b676b3-5b9f-4d04-82ae-f9db3029c311","savedPayload":{"position":{"x":40593.55450196466,"y":21938.07510378422},"targetPosition":{"x":40686.19422978413,"y":22284.150080605094},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"de58c7ca-47d9-48f7-a7fc-ad576082d89f","savedPayload":{"position":{"x":40686.19422978413,"y":22284.150080605094},"targetPosition":{"x":40778.84533803481,"y":22630.27462019225},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"739a79d8-37c4-4add-9a85-f5a56c4b1510","savedPayload":{"position":{"x":40778.84533803481,"y":22630.27462019225},"targetPosition":{"x":40874.1988416803,"y":22986.346465704635},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"d0ff413b-dfbc-4f84-a02a-03ab750beea4","savedPayload":{"position":{"x":41893.54634082963,"y":21977.063439550475},"targetPosition":{"x":42021.77913634585,"y":22455.81161228222},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"b3756856-991c-46e9-b8d1-1fd88616b175","savedPayload":{"position":{"x":42021.81316754483,"y":22455.83985723156},"targetPosition":{"x":42372.21439830182,"y":22806.235388260873},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"0081ab16-4738-40b4-abb6-5e0220b5d3ec","savedPayload":{"position":{"x":42372.21356099864,"y":22806.226453474643},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Initial Take Profit","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.4","id":"5eed5f84-8951-4768-8ff6-1f7cadff9bc0","savedPayload":{"position":{"x":42256.46443548883,"y":21792.571687125077},"targetPosition":{"x":42567.906061434565,"y":22177.53357490311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.previous.movingAverage <= 10","id":"ce100174-38ec-45a1-b30e-b9172449b917","savedPayload":{"position":{"x":41321.10027492377,"y":21724.77294104368},"targetPosition":{"x":41377.02401140234,"y":22078.756166789346},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"361254c9-2c48-4971-a21d-a2db49fd6c02","savedPayload":{"position":{"x":41377.02401140234,"y":22078.756166789346},"targetPosition":{"x":41432.96551482378,"y":22432.890715624035},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"ed006cd1-bf42-46ed-b32b-85206d87cd28","savedPayload":{"position":{"x":41432.96551482378,"y":22432.890715624035},"targetPosition":{"x":41490.569748251975,"y":22796.956822782457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.movingAverage <= 30","id":"4f0db7ef-3ce2-45b3-ad50-b3a70caa7148","savedPayload":{"position":{"x":42034.085892478375,"y":21895.784268531883},"targetPosition":{"x":41905.6790727935,"y":22230.309567356013},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ceb437ce-ce7c-4b40-9c66-76a8510b548f","savedPayload":{"position":{"x":41905.6790727935,"y":22230.309567356013},"targetPosition":{"x":41777.11600709778,"y":22565.015554381593},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"703dab03-8dba-4449-ac5d-07237c70801a","savedPayload":{"position":{"x":42485.127772570304,"y":22452.84922303708},"targetPosition":{"x":42131.22517356006,"y":22508.89700539373},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9d989c1e-f5ea-4d99-98f4-cb9f7a2dd7f4","savedPayload":{"position":{"x":42131.22517356006,"y":22508.89700539373},"targetPosition":{"x":41777.11600709778,"y":22565.015554381593},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"0ce6bb24-f168-4aaa-83ed-164c13e57c0d","savedPayload":{"position":{"x":41777.11600709778,"y":22565.015554381593},"targetPosition":{"x":41490.569748251975,"y":22796.956822782457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"faa1df3b-d813-4285-be96-e3f6708ade25","savedPayload":{"position":{"x":42745.46977372124,"y":21714.804795770884},"targetPosition":{"x":42567.906061434565,"y":22177.53357490311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"1ce94879-9960-47dd-8014-32609b061a2d","savedPayload":{"position":{"x":42568.00695592488,"y":22177.51975436304},"targetPosition":{"x":42645.246053418836,"y":22667.102763559975},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e7378dd3-01ab-4cc5-a2b6-158a1d106b8c","savedPayload":{"position":{"x":42645.246522085334,"y":22667.07588906284},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionSize":{"type":"Position Size","name":"Position Size","formula":{"type":"Formula","code":"balanceBaseAsset","id":"0d807c1a-4d63-4f34-8b21-601fb4bcbfb7","savedPayload":{"position":{"x":43172.29109125882,"y":22274.223797311202},"targetPosition":{"x":42947.393118584485,"y":22715.316464885633},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9a47a9fc-fd8b-4ac0-9ab4-fe8105b48742","savedPayload":{"position":{"x":42947.393961295784,"y":22715.289411267244},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionRate":{"type":"Position Rate","name":"Position Rate","formula":{"type":"Formula","name":"My Formula","code":"candle.close","id":"a2b79f22-8024-4357-b21e-a5bb2f06085a","savedPayload":{"position":{"x":43604.59284152999,"y":22707.243882182105},"targetPosition":{"x":43163.40623490247,"y":22931.91537080668},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"1caf887e-9ef2-4c73-89e3-681501c22f2f","savedPayload":{"position":{"x":43163.40569741022,"y":22931.906398251514},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"7066a6ba-4707-439c-9f30-d115b7e91587","savedPayload":{"position":{"x":42722.49708483028,"y":23156.165399772814},"targetPosition":{"x":42567.46100856192,"y":24134.750300215164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"openExecution":{"type":"Open Execution","id":"12807f32-bcd9-479d-ba07-b4cc6559a788","savedPayload":{"position":{"x":42967.816253548626,"y":23844.006750298126},"targetPosition":{"x":42567.46100856192,"y":24134.750300215164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Open","code":"{ \n\"text\": \"Open Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"Initial stop loss at $\" + stopLoss.toFixed(0) + \" and take profit at $\" + takeProfit.toFixed(0) + \". Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"b2207d78-4a23-497d-8d5f-9b96578cc2f2","savedPayload":{"position":{"x":43545.39029452099,"y":24289.313990316892},"targetPosition":{"x":43056.470377660335,"y":24212.05286087909},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"1d7fbd95-3af3-46ba-be2c-82afa907bafd","savedPayload":{"position":{"x":43056.470377660335,"y":24212.05286087909},"targetPosition":{"x":42567.46100856192,"y":24134.750300215164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"039258ce-13eb-4b32-86ef-201855c44942","savedPayload":{"position":{"x":42567.46100856192,"y":24134.750300215164},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"manageStage":{"type":"Manage Stage","stopLoss":{"type":"Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 5.25 / 100 < stopLoss) {chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 5.25 / 100} else {stopLoss}","id":"45d6af45-3e8b-4896-b836-2d83f93e32a7","savedPayload":{"position":{"x":43040.85349551532,"y":24865.41827961376},"targetPosition":{"x":42804.32840344481,"y":25481.304974122737},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[],"announcements":[],"id":"17dc17a5-0393-42d9-a481-652f7a81d656","savedPayload":{"position":{"x":43220.02334011708,"y":25994.393030930725},"targetPosition":{"x":42804.32840344481,"y":25481.304974122737},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"4f1b38dc-b338-475c-9127-5a9e3fcecc41","savedPayload":{"position":{"x":42804.32840344481,"y":25481.304974122737},"targetPosition":{"x":42151.50515206838,"y":25584.614309622168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"557b2149-e296-4b80-aaf1-370725a83a54","savedPayload":{"position":{"x":42151.50515206838,"y":25584.614309622168},"targetPosition":{"x":41498.80494613707,"y":25687.903975235113},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Take Profit","phases":[{"type":"Phase","name":"P 1","formula":{"type":"Formula","name":"New Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.15 - chart.at01hs.bollingerBand.deviation * 4 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.15 - chart.at01hs.bollingerBand.deviation * 4} else {takeProfit}","id":"21425aee-b9db-4dcc-8857-a2192910d396","savedPayload":{"position":{"x":43554.71815666318,"y":26651.942851303353},"targetPosition":{"x":43102.490029688415,"y":26853.17443468902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.previous.movingAverage <= 15","id":"b7fe3cd8-6a2b-497f-a55b-790584961b57","savedPayload":{"position":{"x":39076.46058213071,"y":27754.396568633903},"targetPosition":{"x":39257.34107582367,"y":27505.91308833616},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a12da025-d126-479e-8557-1eb2822f4f0f","savedPayload":{"position":{"x":39257.34107582367,"y":27505.91308833616},"targetPosition":{"x":39438.07303747677,"y":27257.00491756352},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"59701135-40be-49ae-b079-8e4b6310f6ea","savedPayload":{"position":{"x":38913.1938983026,"y":27578.244758070996},"targetPosition":{"x":39175.626335632536,"y":27417.733114613016},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4f747a40-0047-47f4-8a13-bd0521b95e2b","savedPayload":{"position":{"x":39175.626335632536,"y":27417.733114613016},"targetPosition":{"x":39438.07303747677,"y":27257.00491756352},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >= 5","id":"bc4571b1-a0a8-45cf-b77b-14576d3ad21f","savedPayload":{"position":{"x":38830.05510220841,"y":27353.037657790377},"targetPosition":{"x":39134.026162947055,"y":27305.00124396789},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"fb89b159-fe69-404b-b013-bb82e2fdcfa1","savedPayload":{"position":{"x":39134.026162947055,"y":27305.00124396789},"targetPosition":{"x":39438.07303747677,"y":27257.00491756352},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"67030efa-0733-4e0e-a66c-c7dd1c68417f","savedPayload":{"position":{"x":39438.07303747677,"y":27257.00491756352},"targetPosition":{"x":39708.61223080854,"y":27090.915796338108},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.movingAverage <= 40 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 3","id":"4bb6b468-bd77-474e-93b2-f86437fd46c7","savedPayload":{"position":{"x":38934.982734317826,"y":27128.807942747462},"targetPosition":{"x":39087.0070867157,"y":27104.959222048245},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"95eecb05-95b8-4460-9327-a1b3e42acb6e","savedPayload":{"position":{"x":39087.0070867157,"y":27104.959222048245},"targetPosition":{"x":39390.90528515823,"y":27057.147755850645},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.direction === \"Up\"","id":"8b544008-7648-457a-b75e-b4fee8ed097c","savedPayload":{"position":{"x":38816.80662927576,"y":26836.626934486252},"targetPosition":{"x":39103.694982618494,"y":26946.725109026145},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7a8ac438-7d80-4cc0-93bb-de47b2fadfd0","savedPayload":{"position":{"x":39103.694982618494,"y":26946.725109026145},"targetPosition":{"x":39390.90528515823,"y":27057.147755850645},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"d8a07730-dec5-4ebd-8de1-dc60a1197b04","savedPayload":{"position":{"x":39390.90528515823,"y":27057.147755850645},"targetPosition":{"x":39708.61223080854,"y":27090.915796338108},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"76c3b030-4744-4005-bcf8-2fe69a68f3ae","savedPayload":{"position":{"x":42999.10513705871,"y":27837.56606790635},"targetPosition":{"x":43102.490029688415,"y":26853.17443468902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"691dde4a-1ec7-4bf7-aa4a-13697345bbff","savedPayload":{"position":{"x":43102.490029688415,"y":26853.17443468902},"targetPosition":{"x":42300.66701596131,"y":26270.601123532455},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 2","formula":{"type":"Formula","name":"New Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.1 - chart.at01hs.bollingerBand.deviation * 4 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.1 - chart.at01hs.bollingerBand.deviation * 4} else {takeProfit}","id":"0126f4db-54fc-4ede-8373-455eb98c02ac","savedPayload":{"position":{"x":44356.46025768742,"y":27234.418120667342},"targetPosition":{"x":43904.27034596599,"y":27435.680274939747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at12hs.percentageBandwidth.previous.movingAverage <= 15","id":"dd2c179d-0f15-4260-807e-926d1af2bb29","savedPayload":{"position":{"x":39020.18668533981,"y":28388.812797834158},"targetPosition":{"x":39185.78202780981,"y":28121.28941849295},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"65617019-982d-40a3-a086-b31b540babc3","savedPayload":{"position":{"x":39185.78202780981,"y":28121.28941849295},"targetPosition":{"x":39366.632975030356,"y":27872.99503392311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":" chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"cb1c258b-8d57-4eb9-b912-5c8924cd4d49","savedPayload":{"position":{"x":38841.49821734451,"y":28193.801860912947},"targetPosition":{"x":39104.11952541188,"y":28033.451648490387},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"52c0dae3-b8e3-405b-9423-f32913636a57","savedPayload":{"position":{"x":39104.11952541188,"y":28033.451648490387},"targetPosition":{"x":39366.632975030356,"y":27872.99503392311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >= 15","id":"89d19b8f-d030-46f6-8a98-c2bfa2e1f553","savedPayload":{"position":{"x":38758.59117340723,"y":27968.676415639282},"targetPosition":{"x":39062.50197271419,"y":27920.95845222073},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c13131f3-4bfe-43c6-8ed4-7a6fd8a385d1","savedPayload":{"position":{"x":39062.50197271419,"y":27920.95845222073},"targetPosition":{"x":39366.632975030356,"y":27872.99503392311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"14c028eb-4857-45dd-a5e1-32428d869fc9","savedPayload":{"position":{"x":39366.632975030356,"y":27872.99503392311},"targetPosition":{"x":39644.57846953069,"y":27702.333527444905},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 70 || chart.at12hs.percentageBandwidth.movingAverage <= 40 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"2094c0d3-3283-4228-8b69-8db579c64ed9","savedPayload":{"position":{"x":38849.82406221925,"y":27734.232868776628},"targetPosition":{"x":39002.18453621761,"y":27710.24535513939},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"70d80cd1-296e-47b3-b1c1-5b6b7a62bbc6","savedPayload":{"position":{"x":39002.18453621761,"y":27710.24535513939},"targetPosition":{"x":39318.43147689118,"y":27667.80758277534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"b44eb9ba-94f2-4231-88ea-ece9767764d2","savedPayload":{"position":{"x":38743.64438683522,"y":27447.363629044692},"targetPosition":{"x":39031.25127631588,"y":27557.529013013213},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"37903197-40db-4925-b0eb-8a5200605fcb","savedPayload":{"position":{"x":39031.25127631588,"y":27557.529013013213},"targetPosition":{"x":39318.43147689118,"y":27667.80758277534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"eb62450a-7c7d-461b-853c-18c9d9256db2","savedPayload":{"position":{"x":39318.43147689118,"y":27667.80758277534},"targetPosition":{"x":39644.57846953069,"y":27702.333527444905},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"726e5671-70e4-4489-ba9f-24f19bc3139f","savedPayload":{"position":{"x":43800.89921548829,"y":28419.99703426067},"targetPosition":{"x":43904.27034596599,"y":27435.680274939747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"d465a0e2-291c-44af-a7e7-ab88dfafee33","savedPayload":{"position":{"x":43904.27034596599,"y":27435.680274939747},"targetPosition":{"x":43102.490029688415,"y":26853.17443468902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 3","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 3 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 3} else {takeProfit}","id":"4229e801-d485-4e90-a57e-14d7401dba6b","savedPayload":{"position":{"x":45158.08957392563,"y":27816.911902414922},"targetPosition":{"x":44705.940105036716,"y":28018.16302825531},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 20","id":"1d28c26b-838a-45b3-a9ae-dfd0ff517b4e","savedPayload":{"position":{"x":43671.762597188776,"y":28665.652455724376},"targetPosition":{"x":43344.72209039262,"y":28520.953982631425},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"435d84a7-18b8-4bdd-ac3e-20ec918972ad","savedPayload":{"position":{"x":43344.72209039262,"y":28520.953982631425},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":" chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"532d32c0-8218-46fd-b226-c0d74ffbc59f","savedPayload":{"position":{"x":43597.85750051345,"y":28833.076691652197},"targetPosition":{"x":43331.63332171728,"y":28593.404475720527},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ace92aa6-e1ac-4bea-8469-697cf19d9ca4","savedPayload":{"position":{"x":43331.63332171728,"y":28593.404475720527},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=10","id":"4c814ec0-81f5-41d2-b6de-f7518b0b0928","savedPayload":{"position":{"x":43423.46986322124,"y":28974.029351480076},"targetPosition":{"x":43244.43462992984,"y":28663.76906695555},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9f704e05-6368-413c-a5cb-9186b17d7c70","savedPayload":{"position":{"x":43244.43462992984,"y":28663.76906695555},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.popularEMA.ema20 > chart.at01hs.popularEMA.ema50 || (chart.at01hs.popularEMA.ema50 - chart.at01hs.popularEMA.ema20 < chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 && chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 < chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema20)","id":"b62b16a0-a825-4abb-80d4-0c011943738d","savedPayload":{"position":{"x":43213.87457064216,"y":29054.40075981538},"targetPosition":{"x":43139.47701980421,"y":28703.965527094068},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"56fcf0d9-017b-481d-906e-1d00909c9707","savedPayload":{"position":{"x":43139.47701980421,"y":28703.965527094068},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"f896ea80-b7b0-4d72-b19a-512e8656f22b","savedPayload":{"position":{"x":43065.015970762935,"y":28352.86106371552},"targetPosition":{"x":42832.91695479948,"y":28066.793036095227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"bd0bcb72-0dac-41e4-a89c-4c6cd57daf7a","savedPayload":{"position":{"x":43051.07226566322,"y":29103.087301525506},"targetPosition":{"x":42922.883797168666,"y":28768.03131200254},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"8a601ab7-fee9-49c4-b575-54f33583a59f","savedPayload":{"position":{"x":42922.883797168666,"y":28768.03131200254},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"cb5a576f-8a8a-4ee2-94ad-1df17c72b431","savedPayload":{"position":{"x":42831.43358116719,"y":29149.772948459282},"targetPosition":{"x":42812.88744862709,"y":28791.45664511423},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ce24e5f1-c2fc-48fe-95c5-653d162f4a6c","savedPayload":{"position":{"x":42812.88744862709,"y":28791.45664511423},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=5","id":"664048a7-6e49-4e70-9778-d20b593e407d","savedPayload":{"position":{"x":42608.719226546105,"y":29126.22083911598},"targetPosition":{"x":42701.44309306069,"y":28779.676316086447},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"e00c3690-d6dc-4a50-95de-ccd597b7959a","savedPayload":{"position":{"x":42701.44309306069,"y":28779.676316086447},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.movingAverage <= 60 || chart.at12hs.percentageBandwidth.movingAverage <= 30","id":"9f61c27b-bcbc-4a78-8f6f-4858c9c48593","savedPayload":{"position":{"x":42403.44799963697,"y":29034.637647296153},"targetPosition":{"x":42598.68106458204,"y":28733.805168060902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"27c9dd7c-0456-4ba1-b544-0b0484f6824f","savedPayload":{"position":{"x":42598.68106458204,"y":28733.805168060902},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"0f0c037f-31b9-43ed-9597-0afdca8bc238","savedPayload":{"position":{"x":42794.351374197264,"y":28433.092876235107},"targetPosition":{"x":42832.91695479948,"y":28066.793036095227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.candle.close <= positionRate * 0.8","id":"76951759-0c59-46ba-b12b-381e7ee76a93","savedPayload":{"position":{"x":41989.1026432755,"y":28749.265422429955},"targetPosition":{"x":42267.24784420857,"y":28524.02504592896},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a7b2547e-0b31-42cf-9b0d-689a02f3964e","savedPayload":{"position":{"x":42267.24784420857,"y":28524.02504592896},"targetPosition":{"x":42546.23149631797,"y":28298.292895552117},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1283aaed-707c-47fd-af74-1d4d02a872f9","savedPayload":{"position":{"x":42546.23149631797,"y":28298.292895552117},"targetPosition":{"x":42832.91695479948,"y":28066.793036095227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"bb4d9e1b-8f3c-4ee8-82e6-c2b44bec9fdb","savedPayload":{"position":{"x":44602.606609786315,"y":29002.35937458846},"targetPosition":{"x":44705.94006741386,"y":28018.14131654659},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"8ddb340d-921c-4999-a685-d4d345b74c5f","savedPayload":{"position":{"x":44706.012384131885,"y":28018.116420001486},"targetPosition":{"x":43904.27034596599,"y":27435.680274939747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 4","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.5 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.5} else {takeProfit}","id":"5b65d305-886b-4244-9d5a-fed743f4429b","savedPayload":{"position":{"x":45999.922327496504,"y":28652.661924257347},"targetPosition":{"x":45108.67865429032,"y":28923.49119666455},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 10","id":"492361d1-7ffa-4589-becd-8559c94538dd","savedPayload":{"position":{"x":39468.09436374861,"y":29836.027043103848},"targetPosition":{"x":39435.98417813631,"y":29530.098683426906},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"5f22aa06-b15c-46ae-bc5f-cb01d4786dfe","savedPayload":{"position":{"x":39435.98417813631,"y":29530.098683426906},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"a0f575e8-ba84-4fa1-9b8b-5c9b1f78837b","savedPayload":{"position":{"x":39274.52671234616,"y":29826.963021090767},"targetPosition":{"x":39338.40665267933,"y":29526.02554167459},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"f488cc42-c26f-4881-b7af-33767df2c3ed","savedPayload":{"position":{"x":39338.40665267933,"y":29526.02554167459},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=10","id":"6467a895-b8d3-46f0-8a41-ba3db3134289","savedPayload":{"position":{"x":39097.61406112272,"y":29761.325104994416},"targetPosition":{"x":39251.40258179088,"y":29494.889710785144},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"e756b47a-49c4-475f-9239-98cc21d001e4","savedPayload":{"position":{"x":39251.40258179088,"y":29494.889710785144},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.popularEMA.ema20 > chart.at01hs.popularEMA.ema50 || (chart.at01hs.popularEMA.ema50 - chart.at01hs.popularEMA.ema20 < chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 && chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 < chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema20)","id":"3a95c3c9-027f-4b7f-b16e-d4d7e3c61af2","savedPayload":{"position":{"x":38937.35676051016,"y":29629.96983701126},"targetPosition":{"x":39165.96311307189,"y":29424.166464323942},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7a0c06a1-a4b2-47e7-a6df-b67d8b3a5ede","savedPayload":{"position":{"x":39165.96311307189,"y":29424.166464323942},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"b1eaefe0-b666-4703-90d0-3c390c8d244d","savedPayload":{"position":{"x":39401.51538741613,"y":29223.881664928525},"targetPosition":{"x":39516.114415636366,"y":28924.56410175436},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"e2ac35ad-181e-4b58-84d6-3d9f1722c5aa","savedPayload":{"position":{"x":38783.25839629824,"y":29550.283067363347},"targetPosition":{"x":38976.78761240302,"y":29311.196837337044},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"fd88ff7a-6009-4b82-b5a6-af38cf1b7dd3","savedPayload":{"position":{"x":38976.78761240302,"y":29311.196837337044},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.movingAverage <= 50 || chart.at12hs.percentageBandwidth.movingAverage <= 17.5","id":"915373e8-78fa-4957-b0d6-8a128722bab2","savedPayload":{"position":{"x":38617.92058663264,"y":29364.980604492466},"targetPosition":{"x":38888.083782832815,"y":29218.185716728876},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"6ec11da4-1d56-4fd9-8798-d7d1079f4a2d","savedPayload":{"position":{"x":38888.083782832815,"y":29218.185716728876},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"035d92fc-2871-4ffc-98c0-bd8bb995d01a","savedPayload":{"position":{"x":38565.3796355632,"y":29255.50956141062},"targetPosition":{"x":38855.60331264063,"y":29152.8843117097},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4eb8fa5b-86e5-4e51-94be-cf93bd61e328","savedPayload":{"position":{"x":38855.60331264063,"y":29152.8843117097},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=5","id":"7f7981d8-7b3f-428c-b50b-7dec88963c51","savedPayload":{"position":{"x":38533.54748612064,"y":29109.17600160364},"targetPosition":{"x":38836.05433977321,"y":29053.353120128697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"1a1a49bc-3696-453c-b26e-221b2198a725","savedPayload":{"position":{"x":38836.05433977321,"y":29053.353120128697},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"35438563-6038-400f-a354-20f7e8d080d7","savedPayload":{"position":{"x":39223.48859763672,"y":29054.74047818931},"targetPosition":{"x":39516.114415636366,"y":28924.56410175436},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.candle.close <= positionRate * 0.6","id":"c71d184c-dad3-4487-81e6-5f9d1e2ec6f3","savedPayload":{"position":{"x":38641.95400740201,"y":28589.07353652381},"targetPosition":{"x":38929.0535263983,"y":28699.13205164679},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9bcabc5a-7a66-4e3e-823d-732c443ae1a6","savedPayload":{"position":{"x":38929.0535263983,"y":28699.13205164679},"targetPosition":{"x":39216.33878579554,"y":28809.305968189135},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"d60cbb75-bc28-4e8f-b7ea-a35d736a54fa","savedPayload":{"position":{"x":39216.33878579554,"y":28809.305968189135},"targetPosition":{"x":39516.114415636366,"y":28924.56410175436},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"45c33f40-81fd-4db2-b218-eb8af83f82f2","savedPayload":{"position":{"x":45403.77606315581,"y":29584.400513497458},"targetPosition":{"x":45507.103578874885,"y":28600.259762022102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"e2fc4f36-4fd8-47b3-853a-986780cedddc","savedPayload":{"position":{"x":45507.67229553253,"y":28600.535617469774},"targetPosition":{"x":44705.94006741386,"y":28018.14131654659},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 5","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"eacb3d83-4d09-4208-a388-1398be9577e2","savedPayload":{"position":{"x":46308.52299848638,"y":29182.447480972733},"targetPosition":{"x":45908.046683418026,"y":28891.563771160505},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"moveToPhaseEvents":[],"announcements":[],"id":"4f569eba-e3b6-49b8-9a09-0b340c93aae5","savedPayload":{"position":{"x":45908.046683418026,"y":28891.563771160505},"targetPosition":{"x":45507.103578874885,"y":28600.259762022102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"4bd6d7c6-5f77-4388-b95b-f6de69aaf635","savedPayload":{"position":{"x":42300.66701596131,"y":26270.601123532455},"targetPosition":{"x":41498.80494613707,"y":25687.903975235113},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[{"type":"Announcement","name":"Manage","code":"{ \n\"text\": \"Manage Stage: you will get the proper message when the trading session is run at a time frame lower than 1H.\",\n\"valueVariantion\": 2.5\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB Trade Management Status! Current TP, current price, and stop: \" + takeProfit.toFixed(0) + \", \" + chart.at01min.candle.close.toFixed(0) + \", \" + stopLoss.toFixed(0) + \".\"","id":"15dd5086-999b-47bd-bc1b-52d237404446","savedPayload":{"position":{"x":41302.81204154982,"y":26447.212699873733},"targetPosition":{"x":41240.26395076143,"y":26049.428184312597},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"874036e1-7a51-4ac3-8b0e-4c9c4b2685d3","savedPayload":{"position":{"x":41601.81126025612,"y":26339.27649608335},"targetPosition":{"x":41498.80494613707,"y":25687.903975235113},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"9dfe21e5-b773-4d3c-8e32-1aaabcb8b62d","savedPayload":{"position":{"x":41498.80494613707,"y":25687.903975235113},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"closeStage":{"type":"Close Stage","closeExecution":{"type":"Close Execution","id":"1a51f4d1-0c53-4be4-9a9c-ebc9fe97044a","savedPayload":{"position":{"x":41201.94668943851,"y":26419.243789944743},"targetPosition":{"x":40759.976634935716,"y":25928.030981927273},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Close","code":"{ \n\"text\": \"Close Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB trade closed! P&L: \" + lastTradeProfitLoss.toFixed(2) * 100 + \"%. Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"66a09bf3-bed1-47e2-ba3a-2726e616b369","savedPayload":{"position":{"x":40320.00785816407,"y":26295.120140331164},"targetPosition":{"x":40539.105344753065,"y":25956.82560373922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"80b2983d-42e3-476d-9aa3-dece6d6098d7","savedPayload":{"position":{"x":40400.71750422938,"y":26481.24829109853},"targetPosition":{"x":40759.976634935716,"y":25928.030981927273},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"dc69e8b8-3474-4900-8169-ff524674a608","savedPayload":{"position":{"x":40759.976634935716,"y":25928.030981927273},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f5eefa19-8d3b-4677-951d-9e8b9381cf09","savedPayload":{"position":{"x":40799.25277624297,"y":25299.475621769012},"targetPosition":{"x":39642.04913415892,"y":25299.5},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Strategy","name":"Xtreme","triggerStage":{"type":"Trigger Stage","triggerOn":{"type":"Trigger On Event","situations":[{"type":"Situation","name":"Xtreme","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"3e541a4b-524f-447f-b562-0d95d16367dc","savedPayload":{"position":{"x":38634.2422209234,"y":25114.210476306747},"targetPosition":{"x":38811.7682138299,"y":25265.453656720227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"5608edc7-4669-416c-8683-363416f0bbd6","savedPayload":{"position":{"x":38811.7682138299,"y":25265.453656720227},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"89919e11-81cf-4229-899f-dc51fee4caa3","savedPayload":{"position":{"x":38783.360000752764,"y":24981.69659794209},"targetPosition":{"x":38911.31594665894,"y":25177.24192909973},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"e6bdb405-f8a6-49d8-90f9-b571e1f776a2","savedPayload":{"position":{"x":38911.31594665894,"y":25177.24192909973},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.previous.previous.previous.deviation > 200","id":"4c3fae0d-e6de-43ec-9cfd-001f23493431","savedPayload":{"position":{"x":38963.49978632944,"y":24896.991012787617},"targetPosition":{"x":39031.45155996245,"y":25120.849731759805},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"262623d8-9d87-4af1-972b-cdc7f9f3dabd","savedPayload":{"position":{"x":39031.45155996245,"y":25120.849731759805},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"9dc33bbb-b6de-42a3-a2f5-559063a5366d","savedPayload":{"position":{"x":39159.762500636134,"y":24867.2298414088},"targetPosition":{"x":39162.11536447005,"y":25101.444106837534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"450315b6-9cf1-4a21-bd7b-1e22b9e925b2","savedPayload":{"position":{"x":39162.11536447005,"y":25101.444106837534},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"3d3b51aa-5993-4a61-bfa3-9cfdedd94c98","savedPayload":{"position":{"x":39361.235711126326,"y":24896.513150786956},"targetPosition":{"x":39296.664078886526,"y":25121.972859979345},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"2e77e2ad-b82f-43aa-b4d4-409159aeae26","savedPayload":{"position":{"x":39296.664078886526,"y":25121.972859979345},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"71c5e77c-8ba2-4c8c-a034-6bd853c5c896","savedPayload":{"position":{"x":39541.83907151459,"y":24974.451872727255},"targetPosition":{"x":39416.7244549055,"y":25172.407687215775},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"14a4c5b3-49bf-4329-bc74-d01161a2753a","savedPayload":{"position":{"x":39416.7244549055,"y":25172.407687215775},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"ea735cd1-88f9-4298-9ae2-9194149470d8","savedPayload":{"position":{"x":39693.465873832836,"y":25103.670977960774},"targetPosition":{"x":39517.81721306262,"y":25258.44903153187},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"d475020d-507d-4b11-959d-139be28e7bb7","savedPayload":{"position":{"x":39517.81721306262,"y":25258.44903153187},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"if (chart.at01hs.bollingerSubChannel.slope != \"Extreme\") {true} else {if (chart.at01hs.bollingerSubChannel.period <=7) {true} else {false}}","id":"43e66d7d-4647-4c4a-9184-716fec0da81e","savedPayload":{"position":{"x":39802.62864091867,"y":25270.589574730027},"targetPosition":{"x":39590.60564965547,"y":25369.650068902545},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"8a0dc00d-c225-4b20-8db7-179f82dd15c0","savedPayload":{"position":{"x":39590.60564965547,"y":25369.650068902545},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.previous.movingAverage >= 70","id":"12e94fef-d550-4d40-91b6-7ab599418e2b","savedPayload":{"position":{"x":39860.42814199552,"y":25461.695547089388},"targetPosition":{"x":39629.17175511788,"y":25496.90542877681},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"f1431d9b-f0ad-4ac2-a389-19aefd45ae65","savedPayload":{"position":{"x":39629.17175511788,"y":25496.90542877681},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at24hs.bollingerBand.deviation > 2000","id":"3ea73407-524a-4d18-8e64-cde594389f1c","savedPayload":{"position":{"x":39862.25228331403,"y":25661.08498035626},"targetPosition":{"x":39630.36845143606,"y":25629.79896218831},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"8906bf8b-8852-4ee8-99cf-c7012e501f37","savedPayload":{"position":{"x":39630.36845143606,"y":25629.79896218831},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"c014c368-926e-40d0-90ac-bc42b4030112","savedPayload":{"position":{"x":39861.42977523583,"y":26842.02189422478},"targetPosition":{"x":39371.93722036876,"y":26592.693171942734},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"cc1b7480-4eb6-4e05-97a8-eb0cccf1bec2","savedPayload":{"position":{"x":39371.93722036876,"y":26592.693171942734},"targetPosition":{"x":38882.46479348825,"y":26343.46333561765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"triggerOff":{"type":"Trigger Off Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close > chart.at01hs.bollingerBand.movingAverage","id":"ac1a8358-509d-4a6b-ae6c-5c089c5b394d","savedPayload":{"position":{"x":39854.427602718395,"y":27081.074015003174},"targetPosition":{"x":39640.56634645905,"y":26985.882462866124},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"efdeab3d-33ab-402e-865e-fc74a1f882f3","savedPayload":{"position":{"x":39640.56634645905,"y":26985.882462866124},"targetPosition":{"x":39301.72890316699,"y":26835.062612435067},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"31853984-3108-4bd0-aa9a-8b2c3bdbe432","savedPayload":{"position":{"x":39222.01208389953,"y":27388.268767037673},"targetPosition":{"x":39052.21505392141,"y":26865.808845845586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"be56ec91-bf3a-4888-9962-403640a3bc9f","savedPayload":{"position":{"x":39052.21505392141,"y":26865.808845845586},"targetPosition":{"x":38882.46479348825,"y":26343.46333561765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takePosition":{"type":"Take Position Event","situations":[{"type":"Situation","name":"Xtreme","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"58ee7e60-f4ae-4b57-b5c6-11aba949230a","savedPayload":{"position":{"x":39154.73823641365,"y":27232.789230778213},"targetPosition":{"x":38922.04306999821,"y":27208.34497137087},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a0c2192c-6c72-4c29-b00a-72b7c8cb4d10","savedPayload":{"position":{"x":38922.04306999821,"y":27208.34497137087},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"5f3ec561-949c-459e-9463-a9444b112985","savedPayload":{"position":{"x":39105.94680751818,"y":27414.96822571868},"targetPosition":{"x":38892.1048115828,"y":27319.653919703236},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"d6e21f88-645e-4cbb-8888-233a48c0874e","savedPayload":{"position":{"x":38892.1048115828,"y":27319.653919703236},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.previous.previous.previous.deviation > 200","id":"a5dc90e1-f251-403d-901b-4b5f2168671a","savedPayload":{"position":{"x":39002.73281129333,"y":27573.51650874495},"targetPosition":{"x":38829.02746517212,"y":27417.036717538103},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7d61e222-4ca9-4269-9ac8-7d3dd5f7abb0","savedPayload":{"position":{"x":38829.02746517212,"y":27417.036717538103},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"6f4d0602-2693-4cbd-b9f4-68275f92b000","savedPayload":{"position":{"x":38855.838931195285,"y":27692.77642241698},"targetPosition":{"x":38738.69928780255,"y":27489.843079814895},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a32c8be5-7e22-4a0a-85c8-6544fbe89b2e","savedPayload":{"position":{"x":38738.69928780255,"y":27489.843079814895},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"8a7346fc-2789-42f3-b547-2ec4c51fa8b7","savedPayload":{"position":{"x":38679.100769611716,"y":27760.35658060563},"targetPosition":{"x":38630.465712488534,"y":27531.622673608326},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"598028c6-a326-4a56-a26b-0d6dc532b3a4","savedPayload":{"position":{"x":38630.465712488534,"y":27531.622673608326},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"0f36e63a-b125-4962-93b7-6e9cb43f357b","savedPayload":{"position":{"x":38490.50599310752,"y":27770.532025518165},"targetPosition":{"x":38514.746422009855,"y":27537.45725948166},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"45291209-a1e2-4df8-bae6-073206b96243","savedPayload":{"position":{"x":38514.746422009855,"y":27537.45725948166},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"3f9094d6-34a5-4575-812d-bb207c2ccc3c","savedPayload":{"position":{"x":38307.75351570131,"y":27721.378871604025},"targetPosition":{"x":38402.60603834086,"y":27507.67418373712},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"32b9e05f-7b36-4887-9310-0a7afc9585d6","savedPayload":{"position":{"x":38402.60603834086,"y":27507.67418373712},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"if (chart.at01hs.bollingerSubChannel.slope != \"Extreme\") {true} else {if (chart.at01hs.bollingerSubChannel.period <=7) {true} else {false}}","id":"03ae74e4-b4da-406b-a35b-73def4da55fc","savedPayload":{"position":{"x":38148.88912355448,"y":27617.89873022396},"targetPosition":{"x":38304.902167953085,"y":27444.315539978747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4a56a1ac-087e-4b3e-8c1d-0cd01de90faf","savedPayload":{"position":{"x":38304.902167953085,"y":27444.315539978747},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.previous.movingAverage >= 70","id":"dbf4cb5b-3a7c-45e3-9226-b776dfebd03c","savedPayload":{"position":{"x":38029.99258992764,"y":27470.840781029798},"targetPosition":{"x":38232.099327035125,"y":27354.05074915757},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c0b2dd7b-c260-48ae-b748-fad2c2b40e6a","savedPayload":{"position":{"x":38232.099327035125,"y":27354.05074915757},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"f3c49f4e-bd99-4980-ab18-b99cc12e4880","savedPayload":{"position":{"x":38661.81202607896,"y":27381.320846179507},"targetPosition":{"x":38633.1848732142,"y":26832.729177094534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[{"type":"Announcement","name":"Take Position","code":"{ \n\"text\": \"Take Position Event.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB took position at $\" + chart.at01hs.candle.close.toFixed(0) + \"!\"","id":"97c50b48-5c9a-4294-b058-29ef02d3ae3b","savedPayload":{"position":{"x":38575.52714053305,"y":26906.067731836865},"targetPosition":{"x":38400.22161546643,"y":27100.081102367727},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"b4fff923-83b3-427d-b750-9291f0ae4008","savedPayload":{"position":{"x":38449.40311308975,"y":27036.797637400912},"targetPosition":{"x":38633.1848732142,"y":26832.729177094534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"267e00cd-81fb-40ac-b287-ce322c3c0a72","savedPayload":{"position":{"x":38633.1848732142,"y":26832.729177094534},"targetPosition":{"x":38882.46479348825,"y":26343.46333561765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[],"id":"a54033d0-c494-4bb1-9985-f735f9bbdc76","savedPayload":{"position":{"x":38882.46479348825,"y":26343.46333561765},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"openStage":{"type":"Open Stage","initialDefinition":{"type":"Initial Definition","stopLoss":{"type":"Initial Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (positionRate + positionRate * 3 / 100 > 150) {positionRate + positionRate * 3 / 100} else {150}","id":"0337c80c-4d49-463c-a671-541fbc071998","savedPayload":{"position":{"x":38062.009464331495,"y":27769.222299978934},"targetPosition":{"x":37664.405316160555,"y":27662.580380440922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.previous.close < positionRate - positionRate * 4 / 100","id":"3072fb05-b95d-4f59-9798-f43dc9e39c88","savedPayload":{"position":{"x":37929.98129274946,"y":28753.17220326868},"targetPosition":{"x":37839.44752650823,"y":28415.132360582025},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"bdc0ea0a-76f0-4397-8b81-6eed09dda349","savedPayload":{"position":{"x":37839.44752650823,"y":28415.132360582025},"targetPosition":{"x":37748.755823659034,"y":28076.735371852712},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"99059151-79e7-4d4f-a49a-0d2ca95e3fa4","savedPayload":{"position":{"x":37748.755823659034,"y":28076.735371852712},"targetPosition":{"x":37655.04840520232,"y":27727.298505096143},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"99c91cfe-eb1e-423c-b771-2cb019ab972d","savedPayload":{"position":{"x":37771.06077227574,"y":28060.675431141597},"targetPosition":{"x":37664.405316160555,"y":27662.580380440922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"eaca874c-4cde-4941-ab00-766364d335a2","savedPayload":{"position":{"x":37664.224091102864,"y":27662.903849216225},"targetPosition":{"x":37373.030175627486,"y":27371.680921865627},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"4b526d7b-4fdf-43ca-806a-c7771fe60ec1","savedPayload":{"position":{"x":37373.030175627486,"y":27371.680921865627},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Initial Take Profit","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.4","id":"8652e61e-34e7-45bd-bba7-5330a3070d92","savedPayload":{"position":{"x":37469.08380820336,"y":28214.300184030468},"targetPosition":{"x":37210.23596307848,"y":27894.118767508924},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.previous.movingAverage <= 10","id":"28c243c3-4b55-4bb4-a594-4c05e05f9d0c","savedPayload":{"position":{"x":37218.208438274334,"y":28962.60975299573},"targetPosition":{"x":37163.60191935214,"y":28616.78876298102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c1a574e0-c584-4a5a-85c4-8273feb81fe0","savedPayload":{"position":{"x":37163.60191935214,"y":28616.78876298102},"targetPosition":{"x":37108.94332779953,"y":28270.718996895914},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"9cdd45d8-f6c4-4e20-aa65-6a856263e554","savedPayload":{"position":{"x":37108.94332779953,"y":28270.718996895914},"targetPosition":{"x":37052.28832721757,"y":27912.704286551558},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.movingAverage <= 30","id":"bc8ec34b-ca18-42dc-8511-eed81356e134","savedPayload":{"position":{"x":36519.61120085396,"y":28794.624446423844},"targetPosition":{"x":36645.011147517216,"y":28467.775625694398},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"fbbd2165-081d-45b1-aa19-c424816b4326","savedPayload":{"position":{"x":36645.011147517216,"y":28467.775625694398},"targetPosition":{"x":36770.556087479206,"y":28140.723987238274},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"d147395c-8ac0-4a5b-86f4-d013296c62af","savedPayload":{"position":{"x":36078.868902260336,"y":28250.279014350614},"targetPosition":{"x":36424.60756552862,"y":28195.528906634943},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"b5c33632-4126-495f-8b9f-b0833810a6b6","savedPayload":{"position":{"x":36424.60756552862,"y":28195.528906634943},"targetPosition":{"x":36770.556087479206,"y":28140.723987238274},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"3d24dd7c-1b0d-4604-ac3d-13b37c021477","savedPayload":{"position":{"x":36770.556087479206,"y":28140.723987238274},"targetPosition":{"x":37052.28832721757,"y":27912.704286551558},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"4f25e2ba-6306-4d6c-a230-093f16fbaed3","savedPayload":{"position":{"x":37062.584073794125,"y":28278.881608695032},"targetPosition":{"x":37210.23596307848,"y":27894.118767508924},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"fcb8e7e9-eab0-41a4-a33d-c9f8fb5177fb","savedPayload":{"position":{"x":37210.024318125805,"y":27894.36667075418},"targetPosition":{"x":37145.96661083353,"y":27487.416603708327},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"6714ac24-f79f-44b5-924f-8f1f69486b07","savedPayload":{"position":{"x":37145.96661083353,"y":27487.416603708327},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionSize":{"type":"Position Size","name":"Position Size","formula":{"type":"Formula","code":"balanceBaseAsset","id":"3a4a4b24-af2b-4e3c-b78f-7b67e5c615b2","savedPayload":{"position":{"x":36707.72120868308,"y":27814.114056568513},"targetPosition":{"x":36894.797912171234,"y":27447.398318992065},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4a88ee6a-b3a1-4424-8326-2934c76e451a","savedPayload":{"position":{"x":36894.797912171234,"y":27447.398318992065},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionRate":{"type":"Position Rate","name":"Position Rate","formula":{"type":"Formula","name":"My Formula","code":"candle.close","id":"1649c29c-10d2-4766-92e0-d0feedcf24e3","savedPayload":{"position":{"x":36348.37993768211,"y":27454.092178919273},"targetPosition":{"x":36715.21311200159,"y":27267.32295693829},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7fdce54a-55cb-4b4b-8db1-eeafd1028680","savedPayload":{"position":{"x":36715.21311200159,"y":27267.32295693829},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"bffe2cff-a470-41cd-9424-682cde958799","savedPayload":{"position":{"x":37081.9249212183,"y":27080.755302498605},"targetPosition":{"x":37210.78333675819,"y":26267.55164107282},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"openExecution":{"type":"Open Execution","id":"6def1ff4-f839-45e1-847e-e5e9d45ddc65","savedPayload":{"position":{"x":36877.83851612586,"y":26509.252362186733},"targetPosition":{"x":37210.78333675819,"y":26267.55164107282},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Open","code":"{ \n\"text\": \"Open Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"Initial stop loss at $\" + stopLoss.toFixed(0) + \" and take profit at $\" + takeProfit.toFixed(0) + \". Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"7c07a075-202b-4958-83a0-b5e598dba4bf","savedPayload":{"position":{"x":37290.63126975661,"y":26145.537201432606},"targetPosition":{"x":36985.03042320477,"y":26097.257521179734},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"e96b9964-fc96-40ec-b762-f3f7d259ad49","savedPayload":{"position":{"x":36804.1024882504,"y":26203.31469524569},"targetPosition":{"x":37210.78333675819,"y":26267.55164107282},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"b9054c44-a372-44c6-ad59-45520ac8a9ed","savedPayload":{"position":{"x":37210.78333675819,"y":26267.55164107282},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"manageStage":{"type":"Manage Stage","stopLoss":{"type":"Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 4 / 100 < stopLoss) {chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 4 / 100} else {stopLoss}","id":"349bbdc8-b091-4378-bb0a-57537aff415f","savedPayload":{"position":{"x":36817.428085791486,"y":25660.592414338284},"targetPosition":{"x":37014.1484819643,"y":25148.57178806651},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[],"announcements":[],"id":"22ebcfc5-e401-4946-b3e9-4cfb0d667be6","savedPayload":{"position":{"x":36668.500513310195,"y":24722.118116395508},"targetPosition":{"x":37014.1484819643,"y":25148.57178806651},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"c0eddbff-148a-4eb1-b979-e734b2f80b8d","savedPayload":{"position":{"x":37014.1484819643,"y":25148.57178806651},"targetPosition":{"x":37556.71902464847,"y":25062.695102924572},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"5355af46-22d9-45b8-9dc2-62db0d70dc22","savedPayload":{"position":{"x":37556.71902464847,"y":25062.695102924572},"targetPosition":{"x":38099.15732398546,"y":24976.757582524697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Take Profit","phases":[{"type":"Phase","name":"New Phase 1","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.3","id":"56242029-b63d-42a2-af1d-bad6099fc58c","savedPayload":{"position":{"x":36612.39912796038,"y":24336.747274231133},"targetPosition":{"x":36988.52935666488,"y":24169.481133182588},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at04hs.percentageBandwidth.previous.movingAverage <= 15","id":"cdbf6c47-8430-4556-8fde-0ef4d71149f8","savedPayload":{"position":{"x":37045.66609825689,"y":23011.079239241615},"targetPosition":{"x":37049.59238641556,"y":23185.938465619744},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2479eba5-be67-4727-937b-15860346a7ce","savedPayload":{"position":{"x":37049.59238641556,"y":23185.938465619744},"targetPosition":{"x":37053.798753077754,"y":23361.699146030118},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"9dad5a0d-5aa2-4f0c-86d7-ee1421a92e22","savedPayload":{"position":{"x":37053.798753077754,"y":23361.699146030118},"targetPosition":{"x":37063.28410709585,"y":23723.567918757064},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at04hs.percentageBandwidth.movingAverage <= 50","id":"123f1320-0f5b-4c25-831c-a3c6dde68530","savedPayload":{"position":{"x":37528.16287653762,"y":22776.94520156177},"targetPosition":{"x":37402.33856572435,"y":23103.98069617015},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2f7f419b-9b00-466f-908a-31021c5a6f0c","savedPayload":{"position":{"x":37402.33856572435,"y":23103.98069617015},"targetPosition":{"x":37276.72671560257,"y":23430.78943207519},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"d58440e5-c014-4484-a7ac-6e4ed9a0c1b3","savedPayload":{"position":{"x":37821.363061400385,"y":22990.24072991842},"targetPosition":{"x":37549.0450213447,"y":23210.690957570576},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"3b33e925-4371-4070-bfc7-968fc18f04fe","savedPayload":{"position":{"x":37549.0450213447,"y":23210.690957570576},"targetPosition":{"x":37276.72671560257,"y":23430.78943207519},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e37f4996-17a5-4846-bd49-cbb1f6b5eb65","savedPayload":{"position":{"x":37276.72671560257,"y":23430.78943207519},"targetPosition":{"x":37063.28410709585,"y":23723.567918757064},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"e6acc8e7-001c-4f6a-8a16-a2df63767dd4","savedPayload":{"position":{"x":37074.51419599796,"y":23350.941553342414},"targetPosition":{"x":36988.52935666488,"y":24169.481133182588},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"b10e345c-aecf-4562-a85e-c345e6e433e5","savedPayload":{"position":{"x":36988.52935666488,"y":24169.481133182588},"targetPosition":{"x":37654.916260101934,"y":24653.743255781377},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 2","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.2","id":"47ac5c54-bcab-4679-bae3-df48476b2e89","savedPayload":{"position":{"x":35946.1086229054,"y":23852.67639929983},"targetPosition":{"x":36322.16487414663,"y":23685.31432683232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at06hs.percentageBandwidth.previous.movingAverage <= 15","id":"d6b1f3ba-ab79-4f7a-9460-7ad56d2f603c","savedPayload":{"position":{"x":36479.019345974244,"y":22599.411387665285},"targetPosition":{"x":36482.86663384189,"y":22774.409781589235},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"af7e89d7-6bf9-45df-a7d6-43a68ab6030f","savedPayload":{"position":{"x":36482.86663384189,"y":22774.409781589235},"targetPosition":{"x":36487.032365404266,"y":22949.976412005042},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a7ba56f6-8080-4c78-a5f7-3a8f0c879643","savedPayload":{"position":{"x":36487.032365404266,"y":22949.976412005042},"targetPosition":{"x":36496.58422710869,"y":23312.033653946226},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at06hs.percentageBandwidth.movingAverage <= 50","id":"0178806d-1922-4f22-90f4-bad53ec87486","savedPayload":{"position":{"x":36961.61240220812,"y":22365.184631110522},"targetPosition":{"x":36835.65403922743,"y":22692.490754070335},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4e7bfa49-fe1b-420e-a0c7-cf2fa021cb29","savedPayload":{"position":{"x":36835.65403922743,"y":22692.490754070335},"targetPosition":{"x":36709.925261896904,"y":23019.437319595327},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"1005f00a-fc60-46ed-9148-821f8ae11dac","savedPayload":{"position":{"x":37350.12677311433,"y":22734.148665188153},"targetPosition":{"x":37029.960460867296,"y":22877.023877882995},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"cc097f0c-d6f0-4604-a6e5-e20bf187950d","savedPayload":{"position":{"x":37029.960460867296,"y":22877.023877882995},"targetPosition":{"x":36709.925261896904,"y":23019.437319595327},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"055acccd-9f4a-4061-8450-2542728bea0d","savedPayload":{"position":{"x":36709.925261896904,"y":23019.437319595327},"targetPosition":{"x":36496.58422710869,"y":23312.033653946226},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"7c4c75d1-66a7-46fe-9d8b-f59127feb4fb","savedPayload":{"position":{"x":36408.12921121267,"y":22866.886167247496},"targetPosition":{"x":36322.16487414663,"y":23685.31432683232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"d4b86789-6853-4e39-87a2-c0db5ba83b57","savedPayload":{"position":{"x":36322.16487414663,"y":23685.31432683232},"targetPosition":{"x":36988.52935666488,"y":24169.481133182588},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 3","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.15","id":"3f765cfe-7c53-4864-9966-2189a50e67d3","savedPayload":{"position":{"x":35279.80885738757,"y":23368.56838345668},"targetPosition":{"x":35655.83443612933,"y":23201.19255471755},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.previous.movingAverage <= 15 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 5","id":"f3eb091e-10d2-42af-a3fd-0f292dd4facd","savedPayload":{"position":{"x":35912.426132011344,"y":22188.02473709919},"targetPosition":{"x":35916.25655923137,"y":22363.348447677257},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"6bc0645d-3c4b-4f51-b6d4-bf64a18c67ad","savedPayload":{"position":{"x":35916.25655923137,"y":22363.348447677257},"targetPosition":{"x":35920.423785958636,"y":22538.67684374441},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1f70959a-6225-418b-a8c0-9f0eb1b25959","savedPayload":{"position":{"x":35920.423785958636,"y":22538.67684374441},"targetPosition":{"x":35929.99672269212,"y":22900.68593225255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.movingAverage <= 40 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 3","id":"7ea7b4d1-afca-4054-a879-f23b8af5f603","savedPayload":{"position":{"x":36332.45374018306,"y":22117.07062196842},"targetPosition":{"x":36269.12594360673,"y":22281.21403283719},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"46770ee9-f4a5-4954-bba6-a81a98ac170d","savedPayload":{"position":{"x":36269.12594360673,"y":22281.21403283719},"targetPosition":{"x":36143.284821448804,"y":22608.34684277255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.direction === \"Up\"","id":"ef2340f6-814f-4b36-bd7b-dda85145602f","savedPayload":{"position":{"x":36688.35895584827,"y":22167.168535872745},"targetPosition":{"x":36415.90984566528,"y":22387.88368661559},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2bd3230e-6d75-40b8-a21d-ae996c0af609","savedPayload":{"position":{"x":36415.90984566528,"y":22387.88368661559},"targetPosition":{"x":36143.284821448804,"y":22608.34684277255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e6d5b689-a6aa-458d-812c-23aa107070f7","savedPayload":{"position":{"x":36143.284821448804,"y":22608.34684277255},"targetPosition":{"x":35929.99672269212,"y":22900.68593225255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"a6666b42-09ab-4e1d-8496-68d75146df17","savedPayload":{"position":{"x":35741.788301955756,"y":22382.811190232384},"targetPosition":{"x":35655.83443612933,"y":23201.19255471755},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"5c382616-0be7-41ce-ba3b-be50f1171cc6","savedPayload":{"position":{"x":35655.83443612933,"y":23201.19255471755},"targetPosition":{"x":36322.16487414663,"y":23685.31432683232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 4","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.1","id":"f1cba918-c516-4a1b-89c6-0cd41c7c51ba","savedPayload":{"position":{"x":34613.50150082444,"y":22884.621673593494},"targetPosition":{"x":34989.484547146545,"y":22717.228144897},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at12hs.percentageBandwidth.previous.movingAverage <= 15 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 15","id":"7a388dfb-f8d5-4db5-b23f-5904cf537693","savedPayload":{"position":{"x":35345.54834782866,"y":21776.558779173272},"targetPosition":{"x":35349.45653262655,"y":21951.944394752234},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4612914d-6253-478a-9f65-2bb8d6b6b971","savedPayload":{"position":{"x":35349.45653262655,"y":21951.944394752234},"targetPosition":{"x":35353.67262658888,"y":22127.323998231765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"404022c9-8d38-41e3-b54e-440efaf4043e","savedPayload":{"position":{"x":35353.67262658888,"y":22127.323998231765},"targetPosition":{"x":35363.33987740086,"y":22489.365901438257},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 70 || chart.at12hs.percentageBandwidth.movingAverage <= 40 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"1ed19248-d020-48cf-8108-720224f865f7","savedPayload":{"position":{"x":35765.689919450415,"y":21705.61672866174},"targetPosition":{"x":35702.37323616819,"y":21869.870719247032},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"5ee85058-ba70-4e9f-8edd-b347db3e9d48","savedPayload":{"position":{"x":35702.37323616819,"y":21869.870719247032},"targetPosition":{"x":35576.559391603776,"y":22197.076412807688},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"7deb0164-b72a-4a5b-b530-d60ab9d17d64","savedPayload":{"position":{"x":36121.60989343169,"y":21755.73157600969},"targetPosition":{"x":35849.16698062161,"y":21976.524346573085},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"13fb342c-0335-40f9-b23d-3db153c33b16","savedPayload":{"position":{"x":35849.16698062161,"y":21976.524346573085},"targetPosition":{"x":35576.559391603776,"y":22197.076412807688},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e9d17467-711c-45f7-8a08-f34da5a9d4d5","savedPayload":{"position":{"x":35576.559391603776,"y":22197.076412807688},"targetPosition":{"x":35363.33987740086,"y":22489.365901438257},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"9963f1b1-f5aa-4cef-b355-1e496edfa9e9","savedPayload":{"position":{"x":35075.55883317493,"y":21898.966732555666},"targetPosition":{"x":34989.61658634904,"y":22717.254029968073},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"840184c3-e47a-4a42-a4d6-ceef67383a54","savedPayload":{"position":{"x":34989.447899426006,"y":22717.03424162746},"targetPosition":{"x":35655.83443612933,"y":23201.19255471755},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 5","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.75","id":"7548b47a-e78d-4679-8187-284a4f662261","savedPayload":{"position":{"x":33947.16212688377,"y":22400.829966740333},"targetPosition":{"x":34323.3327786729,"y":22233.159240527537},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 30 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=10","id":"ef8a4d0d-b3b8-437b-959a-c963f6c89fe3","savedPayload":{"position":{"x":34686.23734319014,"y":21373.562022389164},"targetPosition":{"x":34712.890400933815,"y":21546.67217851684},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"70e23a03-8532-4e6c-a5f7-0532578ee8c4","savedPayload":{"position":{"x":34712.890400933815,"y":21546.67217851684},"targetPosition":{"x":34740.132717953886,"y":21720.801015570323},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"7691f96d-3dd1-46fd-abd1-a0fa7c2e2457","savedPayload":{"position":{"x":34740.132717953886,"y":21720.801015570323},"targetPosition":{"x":34796.82413534809,"y":22077.91417113042},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 60 || chart.at12hs.percentageBandwidth.movingAverage <= 30 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"5aaa360b-617c-433f-bac0-360722424506","savedPayload":{"position":{"x":35260.930066821624,"y":21131.392613299886},"targetPosition":{"x":35135.5728173172,"y":21458.11081641969},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"90f6a6d2-4b9f-47f0-910b-61106d80b107","savedPayload":{"position":{"x":35135.5728173172,"y":21458.11081641969},"targetPosition":{"x":35010.10226864419,"y":21785.087224550778},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"a2607c85-5420-444e-9a11-4eedeb76745f","savedPayload":{"position":{"x":35554.276038028744,"y":21344.656009542323},"targetPosition":{"x":35282.35444746537,"y":21564.829089366543},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"3a75e5f5-3366-4c84-a15d-dc60119e2844","savedPayload":{"position":{"x":35282.35444746537,"y":21564.829089366543},"targetPosition":{"x":35010.10226864419,"y":21785.087224550778},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"3162a795-144e-419d-9323-d70546393310","savedPayload":{"position":{"x":35010.10226864419,"y":21785.087224550778},"targetPosition":{"x":34796.82413534809,"y":22077.91417113042},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"dd63d693-0e99-463a-a988-86287504d827","savedPayload":{"position":{"x":34409.77172681528,"y":21415.162105086772},"targetPosition":{"x":34323.32725745321,"y":22233.14268963072},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"65109409-9152-4cc4-93e7-c33301f6d28c","savedPayload":{"position":{"x":34323.33595340725,"y":22233.193086902567},"targetPosition":{"x":34989.61658634904,"y":22717.254029968073},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 6","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.25","id":"c8800084-2dd6-44b5-b54a-e26a7dd3149a","savedPayload":{"position":{"x":33280.26062062725,"y":21916.058847959946},"targetPosition":{"x":33656.30735592492,"y":21748.61897320079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 30 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=10","id":"ea47f887-5722-46b5-9b97-ac7bedb3ee7b","savedPayload":{"position":{"x":34201.77769468176,"y":20604.192116809576},"targetPosition":{"x":34210.8857044323,"y":20953.745286913818},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"5fa32bce-6d0e-4b93-ab17-dba973cfad1a","savedPayload":{"position":{"x":34210.8857044323,"y":20953.745286913818},"targetPosition":{"x":34220.13697780592,"y":21303.74379169583},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1825ef6b-4396-4635-a9da-3652cd510acd","savedPayload":{"position":{"x":34220.13697780592,"y":21303.74379169583},"targetPosition":{"x":34230.265895908764,"y":21666.041457149666},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 50 || chart.at12hs.percentageBandwidth.movingAverage <= 17.5 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"d19aeeec-1ed3-462b-8a55-b9806a17ad77","savedPayload":{"position":{"x":34777.155360963516,"y":20757.357517018118},"targetPosition":{"x":34610.00340944047,"y":21065.513546063103},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"b731c25b-4899-4198-98bc-a42c5e77cb14","savedPayload":{"position":{"x":34610.00340944047,"y":21065.513546063103},"targetPosition":{"x":34443.29351708951,"y":21373.376150149285},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"3a3e404a-06ca-4371-a5e4-f71d0ceab201","savedPayload":{"position":{"x":34987.71386018262,"y":20932.496636578537},"targetPosition":{"x":34715.44143342088,"y":21153.23638102526},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"188c7a7f-f9b7-4d4e-9147-065d1aa7a2f4","savedPayload":{"position":{"x":34715.44143342088,"y":21153.23638102526},"targetPosition":{"x":34443.29351708951,"y":21373.376150149285},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"869ee575-049f-4759-a498-79647b0ed1a4","savedPayload":{"position":{"x":34443.29351708951,"y":21373.376150149285},"targetPosition":{"x":34230.265895908764,"y":21666.041457149666},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"412e8505-aab3-4c3c-8799-dfddc8e879f2","savedPayload":{"position":{"x":33742.19649676662,"y":20930.31632524261},"targetPosition":{"x":33656.34260136011,"y":21748.59492729729},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"34563f7d-9c72-465b-8265-07302e098ff1","savedPayload":{"position":{"x":33657.17038592897,"y":21749.36153835545},"targetPosition":{"x":34323.32725745321,"y":22233.14268963072},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"e63d85c1-3f43-4eee-96f2-3db5ae3db60e","savedPayload":{"position":{"x":32990.1264608507,"y":21264.6414477228},"targetPosition":{"x":33323.105667745454,"y":21506.49043771999},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"moveToPhaseEvents":[],"announcements":[],"id":"3f98a4e8-b586-4ffa-9630-86caec6508ff","savedPayload":{"position":{"x":33323.097425587905,"y":21506.467085729528},"targetPosition":{"x":33656.34260136011,"y":21748.59492729729},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a3704633-a258-4bcf-9aef-063f91fe4729","savedPayload":{"position":{"x":37654.916260101934,"y":24653.743255781377},"targetPosition":{"x":38099.15732398546,"y":24976.757582524697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[{"type":"Announcement","name":"Manage","code":"{ \n\"text\": \"Manage Stage: you will get the proper message when the trading session is run at a time frame lower than 1H.\",\n\"valueVariantion\": 2.5\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB Trade Management Status! Current TP, current price, and stop: \" + takeProfit.toFixed(0) + \", \" + chart.at01min.candle.close.toFixed(0) + \", \" + stopLoss.toFixed(0) + \".\"","id":"2c2a8de4-7aef-421d-b0c9-cbfcff444c84","savedPayload":{"position":{"x":38376.81204154982,"y":25097.712699873733},"targetPosition":{"x":38314.26395076143,"y":24699.928184312597},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4264495f-73b8-4fb4-a41b-034b0727a9b0","savedPayload":{"position":{"x":38013.68104873196,"y":24434.85298322019},"targetPosition":{"x":38099.15732398546,"y":24976.757582524697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e3e4ef8c-d328-417c-af43-1300f00f674f","savedPayload":{"position":{"x":38099.15732398546,"y":24976.757582524697},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"closeStage":{"type":"Close Stage","closeExecution":{"type":"Close Execution","id":"0b0c1936-fe07-43b9-80dd-8e8d7cb5e19d","savedPayload":{"position":{"x":38599.14393103092,"y":24239.61963606108},"targetPosition":{"x":38713.07429295899,"y":24777.04968466085},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Close","code":"{ \n\"text\": \"Close Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB trade closed! P&L: \" + lastTradeProfitLoss.toFixed(2) * 100 + \"%. Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"d97442f9-5c13-4ad6-9151-878e6842fe57","savedPayload":{"position":{"x":38807.00785816407,"y":25105.620140331164},"targetPosition":{"x":39026.105344753065,"y":24767.32560373922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"8df754d2-5c34-405b-b7ff-95c5f87083d2","savedPayload":{"position":{"x":39011.80785197776,"y":24317.005110283426},"targetPosition":{"x":38713.07429295899,"y":24777.04968466085},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"51a105b6-c1a1-4805-b0f0-a4895097f071","savedPayload":{"position":{"x":38713.07429295899,"y":24777.04968466085},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"3e5c4b18-a27e-4d12-a5d8-9bf44e970f1c","savedPayload":{"position":{"x":38543.28678959614,"y":25299.500783556694},"targetPosition":{"x":39642.04913415892,"y":25299.5},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"126a9ca3-6075-4cb2-a65f-0073c3a0bc00","savedPayload":{"position":{"x":39642.04913415892,"y":25299.5},"targetPosition":{"x":39642.04913415892,"y":25299.5},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"w"}}} \ No newline at end of file +{"type":"Trading System","name":"WHB v.2 (BTC/USDT)","code":"{ \n\"codeName\": \"Sparta-WHB-BTC-USDT\"\n}","tradingStrategies":[{"type":"Trading Strategy","name":"Steep","triggerStage":{"type":"Trigger Stage","triggerOn":{"type":"Trigger On Event","situations":[{"type":"Situation","name":"Steep","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"04630f48-d984-4e80-ae51-614c9b8d8d4a","savedPayload":{"position":{"x":38654.42937601845,"y":24118.7146096835},"targetPosition":{"x":38896.03793282062,"y":23893.50644952296},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"72e53092-78e4-431d-bc2d-7f3079881067","savedPayload":{"position":{"x":38896.03793282062,"y":23893.50644952296},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"874e7f12-9a9f-4d01-9b5a-8d45b3244707","savedPayload":{"position":{"x":38467.02147676738,"y":23830.21107095448},"targetPosition":{"x":38771.169126085806,"y":23701.239147931672},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"639a3d0f-089c-4a72-bb8a-646a088c827f","savedPayload":{"position":{"x":38771.169126085806,"y":23701.239147931672},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"c2044baf-6f85-4b83-9984-efb4e163da43","savedPayload":{"position":{"x":38389.664496650454,"y":23495.04507117375},"targetPosition":{"x":38719.629522672716,"y":23477.904752152226},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"a182ddc2-c81f-4187-a683-e1eb70d2a073","savedPayload":{"position":{"x":38719.629522672716,"y":23477.904752152226},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation > 200","id":"386c8342-029f-43da-934f-842a63abc4cd","savedPayload":{"position":{"x":38431.586955243925,"y":23154.12292223018},"targetPosition":{"x":38747.56097033524,"y":23250.663790011644},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"40bc2e8f-a913-47d3-aedb-6d48c0ba53fa","savedPayload":{"position":{"x":38747.56097033524,"y":23250.663790011644},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"11ac851b-1b7f-4fd2-99ba-e5b374843f75","savedPayload":{"position":{"x":38587.837841833374,"y":22847.685517901587},"targetPosition":{"x":38851.72572661361,"y":23046.50017712293},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"ae77e599-0666-4ee2-9ba1-5454122da57e","savedPayload":{"position":{"x":38851.59129412593,"y":23046.46870027089},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"ce8ed2e0-78f2-4dc5-b67f-7cc0faa6f72d","savedPayload":{"position":{"x":38839.382799047715,"y":22613.10431827154},"targetPosition":{"x":39019.32925227039,"y":22890.193356793647},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"9bd67f05-1e80-4af4-8b3b-6326aea61169","savedPayload":{"position":{"x":39019.20836123038,"y":22890.163662927203},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"5607e471-72db-4adf-b542-5627836b2fc4","savedPayload":{"position":{"x":39156.005778308325,"y":22478.707014444873},"targetPosition":{"x":39230.30291243001,"y":22800.64350413514},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"668f372c-b50d-4ad7-bfe1-cb3594d0b1c9","savedPayload":{"position":{"x":39230.18484547141,"y":22800.612491027987},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope != \"Extreme\" || (chart.at01hs.bollingerSubChannel.period <=6 && chart.at08hs.bollingerChannel.direction === \"Down\" && chart.at06hs.bollingerChannel.direction === \"Down\" && chart.at04hs.bollingerChannel.direction === \"Down\" && chart.at02hs.bollingerSubChannel.slope === (\"Extreme\" || \"Steep\"))","id":"754d3dcd-a660-4a60-b553-60d4e4828fc2","savedPayload":{"position":{"x":39499.05202984986,"y":22460.737831992745},"targetPosition":{"x":39458.97682702611,"y":22788.66468167322},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"d54ee343-d311-4d85-a97f-52b1f68af31c","savedPayload":{"position":{"x":39458.86345462394,"y":22788.633396789402},"targetPosition":{"x":39378.570631146256,"y":23443.53261737075},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"d36638b7-6ffc-4dd8-a0aa-581dc203a3fd","savedPayload":{"position":{"x":39378.570631146256,"y":23443.53261737075},"targetPosition":{"x":39967.429056464694,"y":23743.542886573094},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"eb5ec9e9-b915-41db-917b-b3c4084fa2d0","savedPayload":{"position":{"x":39967.429056464694,"y":23743.542886573094},"targetPosition":{"x":40556.1702605225,"y":24043.408960214012},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"triggerOff":{"type":"Trigger Off Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close > chart.at01hs.bollingerBand.movingAverage","id":"04223c33-3fb5-495e-b481-f3f89d19772d","savedPayload":{"position":{"x":38210.648710788475,"y":24781.418713158193},"targetPosition":{"x":38647.010106152025,"y":24587.43993400261},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"06e77d55-6aff-46fb-940d-673fe9c79bd8","savedPayload":{"position":{"x":38647.010106152025,"y":24587.43993400261},"targetPosition":{"x":39083.52651184902,"y":24393.503671421502},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"8ccdd8c1-1ca7-4746-b504-16ca7dca9164","savedPayload":{"position":{"x":40147.90856068426,"y":22786.295224985486},"targetPosition":{"x":40352.049091964946,"y":23414.93778484698},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"1b2fd09f-1095-4513-b6cd-18f565a96fd2","savedPayload":{"position":{"x":40352.049091964946,"y":23414.93778484698},"targetPosition":{"x":40556.1702605225,"y":24043.408960214012},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takePosition":{"type":"Take Position Event","situations":[{"type":"Situation","name":"Steep","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"f72bf18a-b497-46d4-8be0-e327da01a533","savedPayload":{"position":{"x":39240.350911186295,"y":23504.494295275093},"targetPosition":{"x":39478.87025480116,"y":23522.725227223866},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"8c26040a-2bc1-4227-bede-1970dd8a7d74","savedPayload":{"position":{"x":39478.87025480116,"y":23522.725227223866},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"9fdb21ec-7e23-48f2-bcd8-448147ec07e1","savedPayload":{"position":{"x":39284.59696457764,"y":23305.152539451356},"targetPosition":{"x":39508.373518788445,"y":23389.868800061806},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"9d362d79-a971-4ca6-99db-733d52a345b2","savedPayload":{"position":{"x":39508.373518788445,"y":23389.868800061806},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"6c812d60-985b-402b-bd39-adf518b44d7a","savedPayload":{"position":{"x":39383.2217853877,"y":23126.478191715356},"targetPosition":{"x":39574.09296654021,"y":23270.811905752722},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"b62b951d-9f1d-4ef8-8a1b-8ec6ee301e5e","savedPayload":{"position":{"x":39574.09296654021,"y":23270.811905752722},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation > 200","id":"3c1b1284-cc15-44a0-ad56-152c608873c8","savedPayload":{"position":{"x":39528.19260345359,"y":22982.85266277603},"targetPosition":{"x":39670.68822663726,"y":23175.11183531663},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"d6a25acf-936d-4839-b8ed-aa1f1c26f283","savedPayload":{"position":{"x":39670.68822663726,"y":23175.11183531663},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"a9d0ee08-149f-4e02-b913-e31c6a81143c","savedPayload":{"position":{"x":39707.742010433845,"y":22885.903680709336},"targetPosition":{"x":39790.32484971696,"y":23110.517001753822},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"a1786da7-a63b-4930-a9ac-bde381e03456","savedPayload":{"position":{"x":39790.32484971696,"y":23110.517001753822},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"03ea81ae-6b27-46e6-8355-98418b3102eb","savedPayload":{"position":{"x":39907.31271552981,"y":22843.480362937433},"targetPosition":{"x":39923.30854888421,"y":23082.253933862172},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"7293789d-7e2c-4418-91ff-1773f8300504","savedPayload":{"position":{"x":39923.30854888421,"y":23082.253933862172},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"1cbf188a-3e7e-486b-97f7-591e2942d4c2","savedPayload":{"position":{"x":40110.33565939289,"y":22859.030126104986},"targetPosition":{"x":40058.66797686181,"y":23092.616084393856},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"82ef75a8-d740-4a20-b382-f8a38b67b37f","savedPayload":{"position":{"x":40058.66797686181,"y":23092.616084393856},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope != \"Extreme\" || (chart.at01hs.bollingerSubChannel.period <=6 && chart.at08hs.bollingerChannel.direction === \"Down\" && chart.at06hs.bollingerChannel.direction === \"Down\" && chart.at04hs.bollingerChannel.direction === \"Down\" && chart.at02hs.bollingerSubChannel.slope === (\"Extreme\" || \"Steep\"))","id":"390add95-1bf1-4d10-9ce6-6f0adfdeb11d","savedPayload":{"position":{"x":40301.106300854844,"y":22931.31532649044},"targetPosition":{"x":40185.81529016951,"y":23140.7748187153},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"bad14acd-fac1-47a9-bb8c-35e3fba8017b","savedPayload":{"position":{"x":40185.81529016951,"y":23140.7748187153},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at08hs.bollingerBand.deviation < 1575","id":"5916a300-bd02-4d33-bb25-8132bbab6402","savedPayload":{"position":{"x":40463.76971325339,"y":23054.494110751715},"targetPosition":{"x":40294.25413728789,"y":23222.84607488945},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"70c87419-dd46-48a9-83df-6394dcdc9f81","savedPayload":{"position":{"x":40294.25413728789,"y":23222.84607488945},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at24hs.candle.open < chart.at24hs.bollingerBand.movingAverage + chart.at24hs.bollingerBand.deviation || chart.at24hs.candle.close < chart.at24hs.bollingerBand.movingAverage + chart.at24hs.bollingerBand.deviation","id":"485bc3a9-85ce-42a2-8bc8-3fd8c199a153","savedPayload":{"position":{"x":40559.83786637926,"y":23203.596114575474},"targetPosition":{"x":40375.24653894792,"y":23332.286513695442},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"1dc2cb20-f252-41ac-9617-0ae1115d3883","savedPayload":{"position":{"x":40375.24653894792,"y":23332.286513695442},"targetPosition":{"x":39955.17846091955,"y":23558.926224459294},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a77b35ef-82dd-4a0b-858a-a9332e93ffcd","savedPayload":{"position":{"x":40993.44114336273,"y":22808.180948915793},"targetPosition":{"x":40856.13716335811,"y":23454.689439619513},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[{"type":"Announcement","name":"Take Position","code":"{ \n\"text\": \"Take Position Event.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB took position at $\" + chart.at01hs.candle.close.toFixed(0) + \"!\"","id":"d5a86e8e-555a-4362-9d2f-d5bdf8a4e637","savedPayload":{"position":{"x":41298.2303357878,"y":22964.61172046955},"targetPosition":{"x":41077.186099344915,"y":23209.857438088973},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"207f6150-3902-445b-86c7-1539739421a5","savedPayload":{"position":{"x":41077.15627448108,"y":23209.336531641686},"targetPosition":{"x":40856.13716335811,"y":23454.689439619513},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"ac645fd1-0e1d-4b12-aaf1-3b8e081e847f","savedPayload":{"position":{"x":40856.13716335811,"y":23454.689439619513},"targetPosition":{"x":40556.1702605225,"y":24043.408960214012},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[],"id":"e4de9514-302c-44e1-a9a6-419e43d5e56f","savedPayload":{"position":{"x":40556.1702605225,"y":24043.408960214012},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"openStage":{"type":"Open Stage","initialDefinition":{"type":"Initial Definition","stopLoss":{"type":"Initial Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (positionRate + positionRate * 3 / 100 > 150) {positionRate + positionRate * 3 / 100} else {150}","id":"90875495-204f-47d8-b90a-e740b494d1aa","savedPayload":{"position":{"x":41543.57064293352,"y":22327.617504659756},"targetPosition":{"x":42021.77913634585,"y":22455.81161228222},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.previous.close < positionRate - positionRate * 5 / 100","id":"f0b676b3-5b9f-4d04-82ae-f9db3029c311","savedPayload":{"position":{"x":40593.55450196466,"y":21938.07510378422},"targetPosition":{"x":40686.19422978413,"y":22284.150080605094},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"de58c7ca-47d9-48f7-a7fc-ad576082d89f","savedPayload":{"position":{"x":40686.19422978413,"y":22284.150080605094},"targetPosition":{"x":40778.84533803481,"y":22630.27462019225},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"739a79d8-37c4-4add-9a85-f5a56c4b1510","savedPayload":{"position":{"x":40778.84533803481,"y":22630.27462019225},"targetPosition":{"x":40874.1988416803,"y":22986.346465704635},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"d0ff413b-dfbc-4f84-a02a-03ab750beea4","savedPayload":{"position":{"x":41893.54634082963,"y":21977.063439550475},"targetPosition":{"x":42021.77913634585,"y":22455.81161228222},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"b3756856-991c-46e9-b8d1-1fd88616b175","savedPayload":{"position":{"x":42021.81316754483,"y":22455.83985723156},"targetPosition":{"x":42372.21439830182,"y":22806.235388260873},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"0081ab16-4738-40b4-abb6-5e0220b5d3ec","savedPayload":{"position":{"x":42372.21356099864,"y":22806.226453474643},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Initial Take Profit","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.4","id":"5eed5f84-8951-4768-8ff6-1f7cadff9bc0","savedPayload":{"position":{"x":42256.46443548883,"y":21792.571687125077},"targetPosition":{"x":42567.906061434565,"y":22177.53357490311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.previous.movingAverage <= 10","id":"ce100174-38ec-45a1-b30e-b9172449b917","savedPayload":{"position":{"x":41321.10027492377,"y":21724.77294104368},"targetPosition":{"x":41377.02401140234,"y":22078.756166789346},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"361254c9-2c48-4971-a21d-a2db49fd6c02","savedPayload":{"position":{"x":41377.02401140234,"y":22078.756166789346},"targetPosition":{"x":41432.96551482378,"y":22432.890715624035},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"ed006cd1-bf42-46ed-b32b-85206d87cd28","savedPayload":{"position":{"x":41432.96551482378,"y":22432.890715624035},"targetPosition":{"x":41490.569748251975,"y":22796.956822782457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.movingAverage <= 30","id":"4f0db7ef-3ce2-45b3-ad50-b3a70caa7148","savedPayload":{"position":{"x":42034.085892478375,"y":21895.784268531883},"targetPosition":{"x":41905.6790727935,"y":22230.309567356013},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ceb437ce-ce7c-4b40-9c66-76a8510b548f","savedPayload":{"position":{"x":41905.6790727935,"y":22230.309567356013},"targetPosition":{"x":41777.11600709778,"y":22565.015554381593},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"703dab03-8dba-4449-ac5d-07237c70801a","savedPayload":{"position":{"x":42485.127772570304,"y":22452.84922303708},"targetPosition":{"x":42131.22517356006,"y":22508.89700539373},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9d989c1e-f5ea-4d99-98f4-cb9f7a2dd7f4","savedPayload":{"position":{"x":42131.22517356006,"y":22508.89700539373},"targetPosition":{"x":41777.11600709778,"y":22565.015554381593},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"0ce6bb24-f168-4aaa-83ed-164c13e57c0d","savedPayload":{"position":{"x":41777.11600709778,"y":22565.015554381593},"targetPosition":{"x":41490.569748251975,"y":22796.956822782457},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"faa1df3b-d813-4285-be96-e3f6708ade25","savedPayload":{"position":{"x":42745.46977372124,"y":21714.804795770884},"targetPosition":{"x":42567.906061434565,"y":22177.53357490311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"1ce94879-9960-47dd-8014-32609b061a2d","savedPayload":{"position":{"x":42568.00695592488,"y":22177.51975436304},"targetPosition":{"x":42645.246053418836,"y":22667.102763559975},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e7378dd3-01ab-4cc5-a2b6-158a1d106b8c","savedPayload":{"position":{"x":42645.246522085334,"y":22667.07588906284},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionSize":{"type":"Position Size","name":"Position Size","formula":{"type":"Formula","code":"balanceBaseAsset","id":"0d807c1a-4d63-4f34-8b21-601fb4bcbfb7","savedPayload":{"position":{"x":43172.29109125882,"y":22274.223797311202},"targetPosition":{"x":42947.393118584485,"y":22715.316464885633},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9a47a9fc-fd8b-4ac0-9ab4-fe8105b48742","savedPayload":{"position":{"x":42947.393961295784,"y":22715.289411267244},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionRate":{"type":"Position Rate","name":"Position Rate","formula":{"type":"Formula","name":"My Formula","code":"candle.close","id":"a2b79f22-8024-4357-b21e-a5bb2f06085a","savedPayload":{"position":{"x":43604.59284152999,"y":22707.243882182105},"targetPosition":{"x":43163.40623490247,"y":22931.91537080668},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"1caf887e-9ef2-4c73-89e3-681501c22f2f","savedPayload":{"position":{"x":43163.40569741022,"y":22931.906398251514},"targetPosition":{"x":42722.450287929445,"y":23156.36027557246},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"7066a6ba-4707-439c-9f30-d115b7e91587","savedPayload":{"position":{"x":42722.49708483028,"y":23156.165399772814},"targetPosition":{"x":42567.46100856192,"y":24134.750300215164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"openExecution":{"type":"Open Execution","id":"12807f32-bcd9-479d-ba07-b4cc6559a788","savedPayload":{"position":{"x":42967.816253548626,"y":23844.006750298126},"targetPosition":{"x":42567.46100856192,"y":24134.750300215164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Open","code":"{ \n\"text\": \"Open Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"Initial stop loss at $\" + stopLoss.toFixed(0) + \" and take profit at $\" + takeProfit.toFixed(0) + \". Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"b2207d78-4a23-497d-8d5f-9b96578cc2f2","savedPayload":{"position":{"x":43545.39029452099,"y":24289.313990316892},"targetPosition":{"x":43056.470377660335,"y":24212.05286087909},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"1d7fbd95-3af3-46ba-be2c-82afa907bafd","savedPayload":{"position":{"x":43056.470377660335,"y":24212.05286087909},"targetPosition":{"x":42567.46100856192,"y":24134.750300215164},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"039258ce-13eb-4b32-86ef-201855c44942","savedPayload":{"position":{"x":42567.46100856192,"y":24134.750300215164},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"manageStage":{"type":"Manage Stage","stopLoss":{"type":"Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 5.25 / 100 < stopLoss) {chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 5.25 / 100} else {stopLoss}","id":"45d6af45-3e8b-4896-b836-2d83f93e32a7","savedPayload":{"position":{"x":43040.85349551532,"y":24865.41827961376},"targetPosition":{"x":42804.32840344481,"y":25481.304974122737},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[],"announcements":[],"id":"17dc17a5-0393-42d9-a481-652f7a81d656","savedPayload":{"position":{"x":43220.02334011708,"y":25994.393030930725},"targetPosition":{"x":42804.32840344481,"y":25481.304974122737},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"4f1b38dc-b338-475c-9127-5a9e3fcecc41","savedPayload":{"position":{"x":42804.32840344481,"y":25481.304974122737},"targetPosition":{"x":42151.50515206838,"y":25584.614309622168},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"557b2149-e296-4b80-aaf1-370725a83a54","savedPayload":{"position":{"x":42151.50515206838,"y":25584.614309622168},"targetPosition":{"x":41498.80494613707,"y":25687.903975235113},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Take Profit","phases":[{"type":"Phase","name":"P 1","formula":{"type":"Formula","name":"New Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.15 - chart.at01hs.bollingerBand.deviation * 4 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.15 - chart.at01hs.bollingerBand.deviation * 4} else {takeProfit}","id":"21425aee-b9db-4dcc-8857-a2192910d396","savedPayload":{"position":{"x":43554.71815666318,"y":26651.942851303353},"targetPosition":{"x":43102.490029688415,"y":26853.17443468902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.previous.movingAverage <= 15","id":"b7fe3cd8-6a2b-497f-a55b-790584961b57","savedPayload":{"position":{"x":39076.46058213071,"y":27754.396568633903},"targetPosition":{"x":39257.34107582367,"y":27505.91308833616},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a12da025-d126-479e-8557-1eb2822f4f0f","savedPayload":{"position":{"x":39257.34107582367,"y":27505.91308833616},"targetPosition":{"x":39438.07303747677,"y":27257.00491756352},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"59701135-40be-49ae-b079-8e4b6310f6ea","savedPayload":{"position":{"x":38913.1938983026,"y":27578.244758070996},"targetPosition":{"x":39175.626335632536,"y":27417.733114613016},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4f747a40-0047-47f4-8a13-bd0521b95e2b","savedPayload":{"position":{"x":39175.626335632536,"y":27417.733114613016},"targetPosition":{"x":39438.07303747677,"y":27257.00491756352},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >= 5","id":"bc4571b1-a0a8-45cf-b77b-14576d3ad21f","savedPayload":{"position":{"x":38830.05510220841,"y":27353.037657790377},"targetPosition":{"x":39134.026162947055,"y":27305.00124396789},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"fb89b159-fe69-404b-b013-bb82e2fdcfa1","savedPayload":{"position":{"x":39134.026162947055,"y":27305.00124396789},"targetPosition":{"x":39438.07303747677,"y":27257.00491756352},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"67030efa-0733-4e0e-a66c-c7dd1c68417f","savedPayload":{"position":{"x":39438.07303747677,"y":27257.00491756352},"targetPosition":{"x":39708.61223080854,"y":27090.915796338108},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.movingAverage <= 40 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 3","id":"4bb6b468-bd77-474e-93b2-f86437fd46c7","savedPayload":{"position":{"x":38934.982734317826,"y":27128.807942747462},"targetPosition":{"x":39087.0070867157,"y":27104.959222048245},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"95eecb05-95b8-4460-9327-a1b3e42acb6e","savedPayload":{"position":{"x":39087.0070867157,"y":27104.959222048245},"targetPosition":{"x":39390.90528515823,"y":27057.147755850645},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.direction === \"Up\"","id":"8b544008-7648-457a-b75e-b4fee8ed097c","savedPayload":{"position":{"x":38816.80662927576,"y":26836.626934486252},"targetPosition":{"x":39103.694982618494,"y":26946.725109026145},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7a8ac438-7d80-4cc0-93bb-de47b2fadfd0","savedPayload":{"position":{"x":39103.694982618494,"y":26946.725109026145},"targetPosition":{"x":39390.90528515823,"y":27057.147755850645},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"d8a07730-dec5-4ebd-8de1-dc60a1197b04","savedPayload":{"position":{"x":39390.90528515823,"y":27057.147755850645},"targetPosition":{"x":39708.61223080854,"y":27090.915796338108},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"76c3b030-4744-4005-bcf8-2fe69a68f3ae","savedPayload":{"position":{"x":42999.10513705871,"y":27837.56606790635},"targetPosition":{"x":43102.490029688415,"y":26853.17443468902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"691dde4a-1ec7-4bf7-aa4a-13697345bbff","savedPayload":{"position":{"x":43102.490029688415,"y":26853.17443468902},"targetPosition":{"x":42300.66701596131,"y":26270.601123532455},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 2","formula":{"type":"Formula","name":"New Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.1 - chart.at01hs.bollingerBand.deviation * 4 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.1 - chart.at01hs.bollingerBand.deviation * 4} else {takeProfit}","id":"0126f4db-54fc-4ede-8373-455eb98c02ac","savedPayload":{"position":{"x":44356.46025768742,"y":27234.418120667342},"targetPosition":{"x":43904.27034596599,"y":27435.680274939747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at12hs.percentageBandwidth.previous.movingAverage <= 15","id":"dd2c179d-0f15-4260-807e-926d1af2bb29","savedPayload":{"position":{"x":39020.18668533981,"y":28388.812797834158},"targetPosition":{"x":39185.78202780981,"y":28121.28941849295},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"65617019-982d-40a3-a086-b31b540babc3","savedPayload":{"position":{"x":39185.78202780981,"y":28121.28941849295},"targetPosition":{"x":39366.632975030356,"y":27872.99503392311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":" chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"cb1c258b-8d57-4eb9-b912-5c8924cd4d49","savedPayload":{"position":{"x":38841.49821734451,"y":28193.801860912947},"targetPosition":{"x":39104.11952541188,"y":28033.451648490387},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"52c0dae3-b8e3-405b-9423-f32913636a57","savedPayload":{"position":{"x":39104.11952541188,"y":28033.451648490387},"targetPosition":{"x":39366.632975030356,"y":27872.99503392311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >= 15","id":"89d19b8f-d030-46f6-8a98-c2bfa2e1f553","savedPayload":{"position":{"x":38758.59117340723,"y":27968.676415639282},"targetPosition":{"x":39062.50197271419,"y":27920.95845222073},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c13131f3-4bfe-43c6-8ed4-7a6fd8a385d1","savedPayload":{"position":{"x":39062.50197271419,"y":27920.95845222073},"targetPosition":{"x":39366.632975030356,"y":27872.99503392311},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"14c028eb-4857-45dd-a5e1-32428d869fc9","savedPayload":{"position":{"x":39366.632975030356,"y":27872.99503392311},"targetPosition":{"x":39644.57846953069,"y":27702.333527444905},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 70 || chart.at12hs.percentageBandwidth.movingAverage <= 40 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"2094c0d3-3283-4228-8b69-8db579c64ed9","savedPayload":{"position":{"x":38849.82406221925,"y":27734.232868776628},"targetPosition":{"x":39002.18453621761,"y":27710.24535513939},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"70d80cd1-296e-47b3-b1c1-5b6b7a62bbc6","savedPayload":{"position":{"x":39002.18453621761,"y":27710.24535513939},"targetPosition":{"x":39318.43147689118,"y":27667.80758277534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"b44eb9ba-94f2-4231-88ea-ece9767764d2","savedPayload":{"position":{"x":38743.64438683522,"y":27447.363629044692},"targetPosition":{"x":39031.25127631588,"y":27557.529013013213},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"37903197-40db-4925-b0eb-8a5200605fcb","savedPayload":{"position":{"x":39031.25127631588,"y":27557.529013013213},"targetPosition":{"x":39318.43147689118,"y":27667.80758277534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"eb62450a-7c7d-461b-853c-18c9d9256db2","savedPayload":{"position":{"x":39318.43147689118,"y":27667.80758277534},"targetPosition":{"x":39644.57846953069,"y":27702.333527444905},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"726e5671-70e4-4489-ba9f-24f19bc3139f","savedPayload":{"position":{"x":43800.89921548829,"y":28419.99703426067},"targetPosition":{"x":43904.27034596599,"y":27435.680274939747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"d465a0e2-291c-44af-a7e7-ab88dfafee33","savedPayload":{"position":{"x":43904.27034596599,"y":27435.680274939747},"targetPosition":{"x":43102.490029688415,"y":26853.17443468902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 3","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 3 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 3} else {takeProfit}","id":"4229e801-d485-4e90-a57e-14d7401dba6b","savedPayload":{"position":{"x":45158.08957392563,"y":27816.911902414922},"targetPosition":{"x":44705.940105036716,"y":28018.16302825531},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 20","id":"1d28c26b-838a-45b3-a9ae-dfd0ff517b4e","savedPayload":{"position":{"x":43671.762597188776,"y":28665.652455724376},"targetPosition":{"x":43344.72209039262,"y":28520.953982631425},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"435d84a7-18b8-4bdd-ac3e-20ec918972ad","savedPayload":{"position":{"x":43344.72209039262,"y":28520.953982631425},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":" chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"532d32c0-8218-46fd-b226-c0d74ffbc59f","savedPayload":{"position":{"x":43597.85750051345,"y":28833.076691652197},"targetPosition":{"x":43331.63332171728,"y":28593.404475720527},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ace92aa6-e1ac-4bea-8469-697cf19d9ca4","savedPayload":{"position":{"x":43331.63332171728,"y":28593.404475720527},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=10","id":"4c814ec0-81f5-41d2-b6de-f7518b0b0928","savedPayload":{"position":{"x":43423.46986322124,"y":28974.029351480076},"targetPosition":{"x":43244.43462992984,"y":28663.76906695555},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9f704e05-6368-413c-a5cb-9186b17d7c70","savedPayload":{"position":{"x":43244.43462992984,"y":28663.76906695555},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.popularEMA.ema20 > chart.at01hs.popularEMA.ema50 || (chart.at01hs.popularEMA.ema50 - chart.at01hs.popularEMA.ema20 < chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 && chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 < chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema20)","id":"b62b16a0-a825-4abb-80d4-0c011943738d","savedPayload":{"position":{"x":43213.87457064216,"y":29054.40075981538},"targetPosition":{"x":43139.47701980421,"y":28703.965527094068},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"56fcf0d9-017b-481d-906e-1d00909c9707","savedPayload":{"position":{"x":43139.47701980421,"y":28703.965527094068},"targetPosition":{"x":43065.015970762935,"y":28352.86106371552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"f896ea80-b7b0-4d72-b19a-512e8656f22b","savedPayload":{"position":{"x":43065.015970762935,"y":28352.86106371552},"targetPosition":{"x":42832.91695479948,"y":28066.793036095227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"bd0bcb72-0dac-41e4-a89c-4c6cd57daf7a","savedPayload":{"position":{"x":43051.07226566322,"y":29103.087301525506},"targetPosition":{"x":42922.883797168666,"y":28768.03131200254},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"8a601ab7-fee9-49c4-b575-54f33583a59f","savedPayload":{"position":{"x":42922.883797168666,"y":28768.03131200254},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"cb5a576f-8a8a-4ee2-94ad-1df17c72b431","savedPayload":{"position":{"x":42831.43358116719,"y":29149.772948459282},"targetPosition":{"x":42812.88744862709,"y":28791.45664511423},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"ce24e5f1-c2fc-48fe-95c5-653d162f4a6c","savedPayload":{"position":{"x":42812.88744862709,"y":28791.45664511423},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=5","id":"664048a7-6e49-4e70-9778-d20b593e407d","savedPayload":{"position":{"x":42608.719226546105,"y":29126.22083911598},"targetPosition":{"x":42701.44309306069,"y":28779.676316086447},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"e00c3690-d6dc-4a50-95de-ccd597b7959a","savedPayload":{"position":{"x":42701.44309306069,"y":28779.676316086447},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.movingAverage <= 60 || chart.at12hs.percentageBandwidth.movingAverage <= 30","id":"9f61c27b-bcbc-4a78-8f6f-4858c9c48593","savedPayload":{"position":{"x":42403.44799963697,"y":29034.637647296153},"targetPosition":{"x":42598.68106458204,"y":28733.805168060902},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"27c9dd7c-0456-4ba1-b544-0b0484f6824f","savedPayload":{"position":{"x":42598.68106458204,"y":28733.805168060902},"targetPosition":{"x":42794.351374197264,"y":28433.092876235107},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"0f0c037f-31b9-43ed-9597-0afdca8bc238","savedPayload":{"position":{"x":42794.351374197264,"y":28433.092876235107},"targetPosition":{"x":42832.91695479948,"y":28066.793036095227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.candle.close <= positionRate * 0.8","id":"76951759-0c59-46ba-b12b-381e7ee76a93","savedPayload":{"position":{"x":41989.1026432755,"y":28749.265422429955},"targetPosition":{"x":42267.24784420857,"y":28524.02504592896},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a7b2547e-0b31-42cf-9b0d-689a02f3964e","savedPayload":{"position":{"x":42267.24784420857,"y":28524.02504592896},"targetPosition":{"x":42546.23149631797,"y":28298.292895552117},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1283aaed-707c-47fd-af74-1d4d02a872f9","savedPayload":{"position":{"x":42546.23149631797,"y":28298.292895552117},"targetPosition":{"x":42832.91695479948,"y":28066.793036095227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"bb4d9e1b-8f3c-4ee8-82e6-c2b44bec9fdb","savedPayload":{"position":{"x":44602.606609786315,"y":29002.35937458846},"targetPosition":{"x":44705.94006741386,"y":28018.14131654659},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"8ddb340d-921c-4999-a685-d4d345b74c5f","savedPayload":{"position":{"x":44706.012384131885,"y":28018.116420001486},"targetPosition":{"x":43904.27034596599,"y":27435.680274939747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 4","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.5 > 1){chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.5} else {takeProfit}","id":"5b65d305-886b-4244-9d5a-fed743f4429b","savedPayload":{"position":{"x":45999.922327496504,"y":28652.661924257347},"targetPosition":{"x":45108.67865429032,"y":28923.49119666455},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 10","id":"492361d1-7ffa-4589-becd-8559c94538dd","savedPayload":{"position":{"x":39468.09436374861,"y":29836.027043103848},"targetPosition":{"x":39435.98417813631,"y":29530.098683426906},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"5f22aa06-b15c-46ae-bc5f-cb01d4786dfe","savedPayload":{"position":{"x":39435.98417813631,"y":29530.098683426906},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"a0f575e8-ba84-4fa1-9b8b-5c9b1f78837b","savedPayload":{"position":{"x":39274.52671234616,"y":29826.963021090767},"targetPosition":{"x":39338.40665267933,"y":29526.02554167459},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"f488cc42-c26f-4881-b7af-33767df2c3ed","savedPayload":{"position":{"x":39338.40665267933,"y":29526.02554167459},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=10","id":"6467a895-b8d3-46f0-8a41-ba3db3134289","savedPayload":{"position":{"x":39097.61406112272,"y":29761.325104994416},"targetPosition":{"x":39251.40258179088,"y":29494.889710785144},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"e756b47a-49c4-475f-9239-98cc21d001e4","savedPayload":{"position":{"x":39251.40258179088,"y":29494.889710785144},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.popularEMA.ema20 > chart.at01hs.popularEMA.ema50 || (chart.at01hs.popularEMA.ema50 - chart.at01hs.popularEMA.ema20 < chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 && chart.at01hs.popularEMA.previous.ema50 - chart.at01hs.popularEMA.previous.ema20 < chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 && chart.at01hs.popularEMA.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.ema20 < chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema50 - chart.at01hs.popularEMA.previous.previous.previous.previous.previous.ema20)","id":"3a95c3c9-027f-4b7f-b16e-d4d7e3c61af2","savedPayload":{"position":{"x":38937.35676051016,"y":29629.96983701126},"targetPosition":{"x":39165.96311307189,"y":29424.166464323942},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7a0c06a1-a4b2-47e7-a6df-b67d8b3a5ede","savedPayload":{"position":{"x":39165.96311307189,"y":29424.166464323942},"targetPosition":{"x":39401.51538741613,"y":29223.881664928525},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"b1eaefe0-b666-4703-90d0-3c390c8d244d","savedPayload":{"position":{"x":39401.51538741613,"y":29223.881664928525},"targetPosition":{"x":39516.114415636366,"y":28924.56410175436},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"e2ac35ad-181e-4b58-84d6-3d9f1722c5aa","savedPayload":{"position":{"x":38783.25839629824,"y":29550.283067363347},"targetPosition":{"x":38976.78761240302,"y":29311.196837337044},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"fd88ff7a-6009-4b82-b5a6-af38cf1b7dd3","savedPayload":{"position":{"x":38976.78761240302,"y":29311.196837337044},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.movingAverage <= 50 || chart.at12hs.percentageBandwidth.movingAverage <= 17.5","id":"915373e8-78fa-4957-b0d6-8a128722bab2","savedPayload":{"position":{"x":38617.92058663264,"y":29364.980604492466},"targetPosition":{"x":38888.083782832815,"y":29218.185716728876},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"6ec11da4-1d56-4fd9-8798-d7d1079f4a2d","savedPayload":{"position":{"x":38888.083782832815,"y":29218.185716728876},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.slope === \"Extreme\"","id":"035d92fc-2871-4ffc-98c0-bd8bb995d01a","savedPayload":{"position":{"x":38565.3796355632,"y":29255.50956141062},"targetPosition":{"x":38855.60331264063,"y":29152.8843117097},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4eb8fa5b-86e5-4e51-94be-cf93bd61e328","savedPayload":{"position":{"x":38855.60331264063,"y":29152.8843117097},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.bollingerSubChannel.period >=5","id":"7f7981d8-7b3f-428c-b50b-7dec88963c51","savedPayload":{"position":{"x":38533.54748612064,"y":29109.17600160364},"targetPosition":{"x":38836.05433977321,"y":29053.353120128697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"1a1a49bc-3696-453c-b26e-221b2198a725","savedPayload":{"position":{"x":38836.05433977321,"y":29053.353120128697},"targetPosition":{"x":39216.20118961254,"y":29015.61645459315},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"35438563-6038-400f-a354-20f7e8d080d7","savedPayload":{"position":{"x":39223.48859763672,"y":29054.74047818931},"targetPosition":{"x":39516.114415636366,"y":28924.56410175436},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.candle.close <= positionRate * 0.6","id":"c71d184c-dad3-4487-81e6-5f9d1e2ec6f3","savedPayload":{"position":{"x":38641.95400740201,"y":28589.07353652381},"targetPosition":{"x":38929.0535263983,"y":28699.13205164679},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"9bcabc5a-7a66-4e3e-823d-732c443ae1a6","savedPayload":{"position":{"x":38929.0535263983,"y":28699.13205164679},"targetPosition":{"x":39216.33878579554,"y":28809.305968189135},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"d60cbb75-bc28-4e8f-b7ea-a35d736a54fa","savedPayload":{"position":{"x":39216.33878579554,"y":28809.305968189135},"targetPosition":{"x":39516.114415636366,"y":28924.56410175436},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"45c33f40-81fd-4db2-b218-eb8af83f82f2","savedPayload":{"position":{"x":45403.77606315581,"y":29584.400513497458},"targetPosition":{"x":45507.103578874885,"y":28600.259762022102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"e2fc4f36-4fd8-47b3-853a-986780cedddc","savedPayload":{"position":{"x":45507.67229553253,"y":28600.535617469774},"targetPosition":{"x":44705.94006741386,"y":28018.14131654659},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"P 5","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"eacb3d83-4d09-4208-a388-1398be9577e2","savedPayload":{"position":{"x":46308.52299848638,"y":29182.447480972733},"targetPosition":{"x":45908.046683418026,"y":28891.563771160505},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"moveToPhaseEvents":[],"announcements":[],"id":"4f569eba-e3b6-49b8-9a09-0b340c93aae5","savedPayload":{"position":{"x":45908.046683418026,"y":28891.563771160505},"targetPosition":{"x":45507.103578874885,"y":28600.259762022102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"4bd6d7c6-5f77-4388-b95b-f6de69aaf635","savedPayload":{"position":{"x":42300.66701596131,"y":26270.601123532455},"targetPosition":{"x":41498.80494613707,"y":25687.903975235113},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[{"type":"Announcement","name":"Manage","code":"{ \n\"text\": \"Manage Stage: you will get the proper message when the trading session is run at a time frame lower than 1H.\",\n\"valueVariantion\": 2.5\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB Trade Management Status! Current TP, current price, and stop: \" + takeProfit.toFixed(0) + \", \" + chart.at01min.candle.close.toFixed(0) + \", \" + stopLoss.toFixed(0) + \".\"","id":"15dd5086-999b-47bd-bc1b-52d237404446","savedPayload":{"position":{"x":41302.81204154982,"y":26447.212699873733},"targetPosition":{"x":41240.26395076143,"y":26049.428184312597},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"874036e1-7a51-4ac3-8b0e-4c9c4b2685d3","savedPayload":{"position":{"x":41601.81126025612,"y":26339.27649608335},"targetPosition":{"x":41498.80494613707,"y":25687.903975235113},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"9dfe21e5-b773-4d3c-8e32-1aaabcb8b62d","savedPayload":{"position":{"x":41498.80494613707,"y":25687.903975235113},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"closeStage":{"type":"Close Stage","closeExecution":{"type":"Close Execution","id":"1a51f4d1-0c53-4be4-9a9c-ebc9fe97044a","savedPayload":{"position":{"x":41201.94668943851,"y":26419.243789944743},"targetPosition":{"x":40759.976634935716,"y":25928.030981927273},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Close","code":"{ \n\"text\": \"Close Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB trade closed! P&L: \" + lastTradeProfitLoss.toFixed(2) * 100 + \"%. Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"66a09bf3-bed1-47e2-ba3a-2726e616b369","savedPayload":{"position":{"x":40320.00785816407,"y":26295.120140331164},"targetPosition":{"x":40539.105344753065,"y":25956.82560373922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"80b2983d-42e3-476d-9aa3-dece6d6098d7","savedPayload":{"position":{"x":40400.71750422938,"y":26481.24829109853},"targetPosition":{"x":40759.976634935716,"y":25928.030981927273},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"dc69e8b8-3474-4900-8169-ff524674a608","savedPayload":{"position":{"x":40759.976634935716,"y":25928.030981927273},"targetPosition":{"x":40964.21468152683,"y":25299.495034629625},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"f5eefa19-8d3b-4677-951d-9e8b9381cf09","savedPayload":{"position":{"x":40799.25277624297,"y":25299.475621769012},"targetPosition":{"x":39642.04913415892,"y":25299.5},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Trading Strategy","name":"Xtreme","triggerStage":{"type":"Trigger Stage","triggerOn":{"type":"Trigger On Event","situations":[{"type":"Situation","name":"Xtreme","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"3e541a4b-524f-447f-b562-0d95d16367dc","savedPayload":{"position":{"x":38634.2422209234,"y":25114.210476306747},"targetPosition":{"x":38811.7682138299,"y":25265.453656720227},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"5608edc7-4669-416c-8683-363416f0bbd6","savedPayload":{"position":{"x":38811.7682138299,"y":25265.453656720227},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"89919e11-81cf-4229-899f-dc51fee4caa3","savedPayload":{"position":{"x":38783.360000752764,"y":24981.69659794209},"targetPosition":{"x":38911.31594665894,"y":25177.24192909973},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"e6bdb405-f8a6-49d8-90f9-b571e1f776a2","savedPayload":{"position":{"x":38911.31594665894,"y":25177.24192909973},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.previous.previous.previous.deviation > 200","id":"4c3fae0d-e6de-43ec-9cfd-001f23493431","savedPayload":{"position":{"x":38963.49978632944,"y":24896.991012787617},"targetPosition":{"x":39031.45155996245,"y":25120.849731759805},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"262623d8-9d87-4af1-972b-cdc7f9f3dabd","savedPayload":{"position":{"x":39031.45155996245,"y":25120.849731759805},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"9dc33bbb-b6de-42a3-a2f5-559063a5366d","savedPayload":{"position":{"x":39159.762500636134,"y":24867.2298414088},"targetPosition":{"x":39162.11536447005,"y":25101.444106837534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"450315b6-9cf1-4a21-bd7b-1e22b9e925b2","savedPayload":{"position":{"x":39162.11536447005,"y":25101.444106837534},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"3d3b51aa-5993-4a61-bfa3-9cfdedd94c98","savedPayload":{"position":{"x":39361.235711126326,"y":24896.513150786956},"targetPosition":{"x":39296.664078886526,"y":25121.972859979345},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"2e77e2ad-b82f-43aa-b4d4-409159aeae26","savedPayload":{"position":{"x":39296.664078886526,"y":25121.972859979345},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"71c5e77c-8ba2-4c8c-a034-6bd853c5c896","savedPayload":{"position":{"x":39541.83907151459,"y":24974.451872727255},"targetPosition":{"x":39416.7244549055,"y":25172.407687215775},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"14a4c5b3-49bf-4329-bc74-d01161a2753a","savedPayload":{"position":{"x":39416.7244549055,"y":25172.407687215775},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"ea735cd1-88f9-4298-9ae2-9194149470d8","savedPayload":{"position":{"x":39693.465873832836,"y":25103.670977960774},"targetPosition":{"x":39517.81721306262,"y":25258.44903153187},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"d475020d-507d-4b11-959d-139be28e7bb7","savedPayload":{"position":{"x":39517.81721306262,"y":25258.44903153187},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"if (chart.at01hs.bollingerSubChannel.slope != \"Extreme\") {true} else {if (chart.at01hs.bollingerSubChannel.period <=7) {true} else {false}}","id":"43e66d7d-4647-4c4a-9184-716fec0da81e","savedPayload":{"position":{"x":39802.62864091867,"y":25270.589574730027},"targetPosition":{"x":39590.60564965547,"y":25369.650068902545},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"8a0dc00d-c225-4b20-8db7-179f82dd15c0","savedPayload":{"position":{"x":39590.60564965547,"y":25369.650068902545},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.previous.movingAverage >= 70","id":"12e94fef-d550-4d40-91b6-7ab599418e2b","savedPayload":{"position":{"x":39860.42814199552,"y":25461.695547089388},"targetPosition":{"x":39629.17175511788,"y":25496.90542877681},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"f1431d9b-f0ad-4ac2-a389-19aefd45ae65","savedPayload":{"position":{"x":39629.17175511788,"y":25496.90542877681},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at24hs.bollingerBand.deviation > 2000","id":"3ea73407-524a-4d18-8e64-cde594389f1c","savedPayload":{"position":{"x":39862.25228331403,"y":25661.08498035626},"targetPosition":{"x":39630.36845143606,"y":25629.79896218831},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"8906bf8b-8852-4ee8-99cf-c7012e501f37","savedPayload":{"position":{"x":39630.36845143606,"y":25629.79896218831},"targetPosition":{"x":39167.33696663251,"y":25567.396941744308},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"c014c368-926e-40d0-90ac-bc42b4030112","savedPayload":{"position":{"x":39861.42977523583,"y":26842.02189422478},"targetPosition":{"x":39371.93722036876,"y":26592.693171942734},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"cc1b7480-4eb6-4e05-97a8-eb0cccf1bec2","savedPayload":{"position":{"x":39371.93722036876,"y":26592.693171942734},"targetPosition":{"x":38882.46479348825,"y":26343.46333561765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"triggerOff":{"type":"Trigger Off Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close > chart.at01hs.bollingerBand.movingAverage","id":"ac1a8358-509d-4a6b-ae6c-5c089c5b394d","savedPayload":{"position":{"x":39854.427602718395,"y":27081.074015003174},"targetPosition":{"x":39640.56634645905,"y":26985.882462866124},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"efdeab3d-33ab-402e-865e-fc74a1f882f3","savedPayload":{"position":{"x":39640.56634645905,"y":26985.882462866124},"targetPosition":{"x":39301.72890316699,"y":26835.062612435067},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"31853984-3108-4bd0-aa9a-8b2c3bdbe432","savedPayload":{"position":{"x":39222.01208389953,"y":27388.268767037673},"targetPosition":{"x":39052.21505392141,"y":26865.808845845586},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"be56ec91-bf3a-4888-9962-403640a3bc9f","savedPayload":{"position":{"x":39052.21505392141,"y":26865.808845845586},"targetPosition":{"x":38882.46479348825,"y":26343.46333561765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takePosition":{"type":"Take Position Event","situations":[{"type":"Situation","name":"Xtreme","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage && chart.at01hs.candle.previous.close < chart.at01hs.bollingerBand.previous.movingAverage && chart.at01hs.candle.previous.previous.close < chart.at01hs.bollingerBand.previous.previous.movingAverage","id":"58ee7e60-f4ae-4b57-b5c6-11aba949230a","savedPayload":{"position":{"x":39154.73823641365,"y":27232.789230778213},"targetPosition":{"x":38922.04306999821,"y":27208.34497137087},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a0c2192c-6c72-4c29-b00a-72b7c8cb4d10","savedPayload":{"position":{"x":38922.04306999821,"y":27208.34497137087},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"5f3ec561-949c-459e-9463-a9444b112985","savedPayload":{"position":{"x":39105.94680751818,"y":27414.96822571868},"targetPosition":{"x":38892.1048115828,"y":27319.653919703236},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"d6e21f88-645e-4cbb-8888-233a48c0874e","savedPayload":{"position":{"x":38892.1048115828,"y":27319.653919703236},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.previous.previous.previous.deviation > 200","id":"a5dc90e1-f251-403d-901b-4b5f2168671a","savedPayload":{"position":{"x":39002.73281129333,"y":27573.51650874495},"targetPosition":{"x":38829.02746517212,"y":27417.036717538103},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7d61e222-4ca9-4269-9ac8-7d3dd5f7abb0","savedPayload":{"position":{"x":38829.02746517212,"y":27417.036717538103},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.bollingerChannel.direction === \"Down\"","id":"6f4d0602-2693-4cbd-b9f4-68275f92b000","savedPayload":{"position":{"x":38855.838931195285,"y":27692.77642241698},"targetPosition":{"x":38738.69928780255,"y":27489.843079814895},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"a32c8be5-7e22-4a0a-85c8-6544fbe89b2e","savedPayload":{"position":{"x":38738.69928780255,"y":27489.843079814895},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.max - chart.at01hs.candle.min < 750","id":"8a7346fc-2789-42f3-b547-2ec4c51fa8b7","savedPayload":{"position":{"x":38679.100769611716,"y":27760.35658060563},"targetPosition":{"x":38630.465712488534,"y":27531.622673608326},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"598028c6-a326-4a56-a26b-0d6dc532b3a4","savedPayload":{"position":{"x":38630.465712488534,"y":27531.622673608326},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.bollingerBand.deviation < 1000","id":"0f36e63a-b125-4962-93b7-6e9cb43f357b","savedPayload":{"position":{"x":38490.50599310752,"y":27770.532025518165},"targetPosition":{"x":38514.746422009855,"y":27537.45725948166},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"45291209-a1e2-4df8-bae6-073206b96243","savedPayload":{"position":{"x":38514.746422009855,"y":27537.45725948166},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 30 || chart.at03hs.percentageBandwidth.movingAverage >= 30 || chart.at04hs.percentageBandwidth.movingAverage >= 30 || chart.at06hs.percentageBandwidth.movingAverage >= 30 || chart.at08hs.percentageBandwidth.movingAverage >= 30","id":"3f9094d6-34a5-4575-812d-bb207c2ccc3c","savedPayload":{"position":{"x":38307.75351570131,"y":27721.378871604025},"targetPosition":{"x":38402.60603834086,"y":27507.67418373712},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"32b9e05f-7b36-4887-9310-0a7afc9585d6","savedPayload":{"position":{"x":38402.60603834086,"y":27507.67418373712},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"if (chart.at01hs.bollingerSubChannel.slope != \"Extreme\") {true} else {if (chart.at01hs.bollingerSubChannel.period <=7) {true} else {false}}","id":"03ae74e4-b4da-406b-a35b-73def4da55fc","savedPayload":{"position":{"x":38148.88912355448,"y":27617.89873022396},"targetPosition":{"x":38304.902167953085,"y":27444.315539978747},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4a56a1ac-087e-4b3e-8c1d-0cd01de90faf","savedPayload":{"position":{"x":38304.902167953085,"y":27444.315539978747},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at02hs.percentageBandwidth.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.movingAverage >= 70 || chart.at02hs.percentageBandwidth.previous.previous.previous.previous.previous.movingAverage >= 70","id":"dbf4cb5b-3a7c-45e3-9226-b776dfebd03c","savedPayload":{"position":{"x":38029.99258992764,"y":27470.840781029798},"targetPosition":{"x":38232.099327035125,"y":27354.05074915757},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c0b2dd7b-c260-48ae-b748-fad2c2b40e6a","savedPayload":{"position":{"x":38232.099327035125,"y":27354.05074915757},"targetPosition":{"x":38553.31459023112,"y":27168.682047000915},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"f3c49f4e-bd99-4980-ab18-b99cc12e4880","savedPayload":{"position":{"x":38661.81202607896,"y":27381.320846179507},"targetPosition":{"x":38633.1848732142,"y":26832.729177094534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[{"type":"Announcement","name":"Take Position","code":"{ \n\"text\": \"Take Position Event.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB took position at $\" + chart.at01hs.candle.close.toFixed(0) + \"!\"","id":"97c50b48-5c9a-4294-b058-29ef02d3ae3b","savedPayload":{"position":{"x":38575.52714053305,"y":26906.067731836865},"targetPosition":{"x":38400.22161546643,"y":27100.081102367727},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"b4fff923-83b3-427d-b750-9291f0ae4008","savedPayload":{"position":{"x":38449.40311308975,"y":27036.797637400912},"targetPosition":{"x":38633.1848732142,"y":26832.729177094534},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"267e00cd-81fb-40ac-b287-ce322c3c0a72","savedPayload":{"position":{"x":38633.1848732142,"y":26832.729177094534},"targetPosition":{"x":38882.46479348825,"y":26343.46333561765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[],"id":"a54033d0-c494-4bb1-9985-f735f9bbdc76","savedPayload":{"position":{"x":38882.46479348825,"y":26343.46333561765},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":""}}},"openStage":{"type":"Open Stage","initialDefinition":{"type":"Initial Definition","stopLoss":{"type":"Initial Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (positionRate + positionRate * 3 / 100 > 150) {positionRate + positionRate * 3 / 100} else {150}","id":"0337c80c-4d49-463c-a671-541fbc071998","savedPayload":{"position":{"x":38062.009464331495,"y":27769.222299978934},"targetPosition":{"x":37664.405316160555,"y":27662.580380440922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at01hs.candle.previous.close < positionRate - positionRate * 4 / 100","id":"3072fb05-b95d-4f59-9798-f43dc9e39c88","savedPayload":{"position":{"x":37929.98129274946,"y":28753.17220326868},"targetPosition":{"x":37839.44752650823,"y":28415.132360582025},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"bdc0ea0a-76f0-4397-8b81-6eed09dda349","savedPayload":{"position":{"x":37839.44752650823,"y":28415.132360582025},"targetPosition":{"x":37748.755823659034,"y":28076.735371852712},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"99059151-79e7-4d4f-a49a-0d2ca95e3fa4","savedPayload":{"position":{"x":37748.755823659034,"y":28076.735371852712},"targetPosition":{"x":37655.04840520232,"y":27727.298505096143},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"99c91cfe-eb1e-423c-b771-2cb019ab972d","savedPayload":{"position":{"x":37771.06077227574,"y":28060.675431141597},"targetPosition":{"x":37664.405316160555,"y":27662.580380440922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"eaca874c-4cde-4941-ab00-766364d335a2","savedPayload":{"position":{"x":37664.224091102864,"y":27662.903849216225},"targetPosition":{"x":37373.030175627486,"y":27371.680921865627},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"4b526d7b-4fdf-43ca-806a-c7771fe60ec1","savedPayload":{"position":{"x":37373.030175627486,"y":27371.680921865627},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Initial Take Profit","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.4","id":"8652e61e-34e7-45bd-bba7-5330a3070d92","savedPayload":{"position":{"x":37469.08380820336,"y":28214.300184030468},"targetPosition":{"x":37210.23596307848,"y":27894.118767508924},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.previous.movingAverage <= 10","id":"28c243c3-4b55-4bb4-a594-4c05e05f9d0c","savedPayload":{"position":{"x":37218.208438274334,"y":28962.60975299573},"targetPosition":{"x":37163.60191935214,"y":28616.78876298102},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"c1a574e0-c584-4a5a-85c4-8273feb81fe0","savedPayload":{"position":{"x":37163.60191935214,"y":28616.78876298102},"targetPosition":{"x":37108.94332779953,"y":28270.718996895914},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"9cdd45d8-f6c4-4e20-aa65-6a856263e554","savedPayload":{"position":{"x":37108.94332779953,"y":28270.718996895914},"targetPosition":{"x":37052.28832721757,"y":27912.704286551558},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at03hs.percentageBandwidth.movingAverage <= 30","id":"bc8ec34b-ca18-42dc-8511-eed81356e134","savedPayload":{"position":{"x":36519.61120085396,"y":28794.624446423844},"targetPosition":{"x":36645.011147517216,"y":28467.775625694398},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"fbbd2165-081d-45b1-aa19-c424816b4326","savedPayload":{"position":{"x":36645.011147517216,"y":28467.775625694398},"targetPosition":{"x":36770.556087479206,"y":28140.723987238274},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"d147395c-8ac0-4a5b-86f4-d013296c62af","savedPayload":{"position":{"x":36078.868902260336,"y":28250.279014350614},"targetPosition":{"x":36424.60756552862,"y":28195.528906634943},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"b5c33632-4126-495f-8b9f-b0833810a6b6","savedPayload":{"position":{"x":36424.60756552862,"y":28195.528906634943},"targetPosition":{"x":36770.556087479206,"y":28140.723987238274},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"3d24dd7c-1b0d-4604-ac3d-13b37c021477","savedPayload":{"position":{"x":36770.556087479206,"y":28140.723987238274},"targetPosition":{"x":37052.28832721757,"y":27912.704286551558},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"4f25e2ba-6306-4d6c-a230-093f16fbaed3","savedPayload":{"position":{"x":37062.584073794125,"y":28278.881608695032},"targetPosition":{"x":37210.23596307848,"y":27894.118767508924},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"fcb8e7e9-eab0-41a4-a33d-c9f8fb5177fb","savedPayload":{"position":{"x":37210.024318125805,"y":27894.36667075418},"targetPosition":{"x":37145.96661083353,"y":27487.416603708327},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"6714ac24-f79f-44b5-924f-8f1f69486b07","savedPayload":{"position":{"x":37145.96661083353,"y":27487.416603708327},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionSize":{"type":"Position Size","name":"Position Size","formula":{"type":"Formula","code":"balanceBaseAsset","id":"3a4a4b24-af2b-4e3c-b78f-7b67e5c615b2","savedPayload":{"position":{"x":36707.72120868308,"y":27814.114056568513},"targetPosition":{"x":36894.797912171234,"y":27447.398318992065},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4a88ee6a-b3a1-4424-8326-2934c76e451a","savedPayload":{"position":{"x":36894.797912171234,"y":27447.398318992065},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"positionRate":{"type":"Position Rate","name":"Position Rate","formula":{"type":"Formula","name":"My Formula","code":"candle.close","id":"1649c29c-10d2-4766-92e0-d0feedcf24e3","savedPayload":{"position":{"x":36348.37993768211,"y":27454.092178919273},"targetPosition":{"x":36715.21311200159,"y":27267.32295693829},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"7fdce54a-55cb-4b4b-8db1-eeafd1028680","savedPayload":{"position":{"x":36715.21311200159,"y":27267.32295693829},"targetPosition":{"x":37081.9249212183,"y":27080.755302498605},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"bffe2cff-a470-41cd-9424-682cde958799","savedPayload":{"position":{"x":37081.9249212183,"y":27080.755302498605},"targetPosition":{"x":37210.78333675819,"y":26267.55164107282},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"openExecution":{"type":"Open Execution","id":"6def1ff4-f839-45e1-847e-e5e9d45ddc65","savedPayload":{"position":{"x":36877.83851612586,"y":26509.252362186733},"targetPosition":{"x":37210.78333675819,"y":26267.55164107282},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Open","code":"{ \n\"text\": \"Open Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"Initial stop loss at $\" + stopLoss.toFixed(0) + \" and take profit at $\" + takeProfit.toFixed(0) + \". Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"7c07a075-202b-4958-83a0-b5e598dba4bf","savedPayload":{"position":{"x":37290.63126975661,"y":26145.537201432606},"targetPosition":{"x":36985.03042320477,"y":26097.257521179734},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"e96b9964-fc96-40ec-b762-f3f7d259ad49","savedPayload":{"position":{"x":36804.1024882504,"y":26203.31469524569},"targetPosition":{"x":37210.78333675819,"y":26267.55164107282},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":1,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"b9054c44-a372-44c6-ad59-45520ac8a9ed","savedPayload":{"position":{"x":37210.78333675819,"y":26267.55164107282},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"manageStage":{"type":"Manage Stage","stopLoss":{"type":"Stop","phases":[{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"if (chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 4 / 100 < stopLoss) {chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.movingAverage * 4 / 100} else {stopLoss}","id":"349bbdc8-b091-4378-bb0a-57537aff415f","savedPayload":{"position":{"x":36817.428085791486,"y":25660.592414338284},"targetPosition":{"x":37014.1484819643,"y":25148.57178806651},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[],"announcements":[],"id":"22ebcfc5-e401-4946-b3e9-4cfb0d667be6","savedPayload":{"position":{"x":36668.500513310195,"y":24722.118116395508},"targetPosition":{"x":37014.1484819643,"y":25148.57178806651},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"c0eddbff-148a-4eb1-b979-e734b2f80b8d","savedPayload":{"position":{"x":37014.1484819643,"y":25148.57178806651},"targetPosition":{"x":37556.71902464847,"y":25062.695102924572},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"5355af46-22d9-45b8-9dc2-62db0d70dc22","savedPayload":{"position":{"x":37556.71902464847,"y":25062.695102924572},"targetPosition":{"x":38099.15732398546,"y":24976.757582524697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"takeProfit":{"type":"Take Profit","phases":[{"type":"Phase","name":"New Phase 1","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.3","id":"56242029-b63d-42a2-af1d-bad6099fc58c","savedPayload":{"position":{"x":36612.39912796038,"y":24336.747274231133},"targetPosition":{"x":36988.52935666488,"y":24169.481133182588},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at04hs.percentageBandwidth.previous.movingAverage <= 15","id":"cdbf6c47-8430-4556-8fde-0ef4d71149f8","savedPayload":{"position":{"x":37045.66609825689,"y":23011.079239241615},"targetPosition":{"x":37049.59238641556,"y":23185.938465619744},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2479eba5-be67-4727-937b-15860346a7ce","savedPayload":{"position":{"x":37049.59238641556,"y":23185.938465619744},"targetPosition":{"x":37053.798753077754,"y":23361.699146030118},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"9dad5a0d-5aa2-4f0c-86d7-ee1421a92e22","savedPayload":{"position":{"x":37053.798753077754,"y":23361.699146030118},"targetPosition":{"x":37063.28410709585,"y":23723.567918757064},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at04hs.percentageBandwidth.movingAverage <= 50","id":"123f1320-0f5b-4c25-831c-a3c6dde68530","savedPayload":{"position":{"x":37528.16287653762,"y":22776.94520156177},"targetPosition":{"x":37402.33856572435,"y":23103.98069617015},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2f7f419b-9b00-466f-908a-31021c5a6f0c","savedPayload":{"position":{"x":37402.33856572435,"y":23103.98069617015},"targetPosition":{"x":37276.72671560257,"y":23430.78943207519},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"d58440e5-c014-4484-a7ac-6e4ed9a0c1b3","savedPayload":{"position":{"x":37821.363061400385,"y":22990.24072991842},"targetPosition":{"x":37549.0450213447,"y":23210.690957570576},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"3b33e925-4371-4070-bfc7-968fc18f04fe","savedPayload":{"position":{"x":37549.0450213447,"y":23210.690957570576},"targetPosition":{"x":37276.72671560257,"y":23430.78943207519},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e37f4996-17a5-4846-bd49-cbb1f6b5eb65","savedPayload":{"position":{"x":37276.72671560257,"y":23430.78943207519},"targetPosition":{"x":37063.28410709585,"y":23723.567918757064},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"e6acc8e7-001c-4f6a-8a16-a2df63767dd4","savedPayload":{"position":{"x":37074.51419599796,"y":23350.941553342414},"targetPosition":{"x":36988.52935666488,"y":24169.481133182588},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"b10e345c-aecf-4562-a85e-c345e6e433e5","savedPayload":{"position":{"x":36988.52935666488,"y":24169.481133182588},"targetPosition":{"x":37654.916260101934,"y":24653.743255781377},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":4,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 2","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.2","id":"47ac5c54-bcab-4679-bae3-df48476b2e89","savedPayload":{"position":{"x":35946.1086229054,"y":23852.67639929983},"targetPosition":{"x":36322.16487414663,"y":23685.31432683232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at06hs.percentageBandwidth.previous.movingAverage <= 15","id":"d6b1f3ba-ab79-4f7a-9460-7ad56d2f603c","savedPayload":{"position":{"x":36479.019345974244,"y":22599.411387665285},"targetPosition":{"x":36482.86663384189,"y":22774.409781589235},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"af7e89d7-6bf9-45df-a7d6-43a68ab6030f","savedPayload":{"position":{"x":36482.86663384189,"y":22774.409781589235},"targetPosition":{"x":36487.032365404266,"y":22949.976412005042},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a7ba56f6-8080-4c78-a5f7-3a8f0c879643","savedPayload":{"position":{"x":36487.032365404266,"y":22949.976412005042},"targetPosition":{"x":36496.58422710869,"y":23312.033653946226},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at06hs.percentageBandwidth.movingAverage <= 50","id":"0178806d-1922-4f22-90f4-bad53ec87486","savedPayload":{"position":{"x":36961.61240220812,"y":22365.184631110522},"targetPosition":{"x":36835.65403922743,"y":22692.490754070335},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4e7bfa49-fe1b-420e-a0c7-cf2fa021cb29","savedPayload":{"position":{"x":36835.65403922743,"y":22692.490754070335},"targetPosition":{"x":36709.925261896904,"y":23019.437319595327},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.direction === \"Up\"","id":"1005f00a-fc60-46ed-9148-821f8ae11dac","savedPayload":{"position":{"x":37350.12677311433,"y":22734.148665188153},"targetPosition":{"x":37029.960460867296,"y":22877.023877882995},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"cc097f0c-d6f0-4604-a6e5-e20bf187950d","savedPayload":{"position":{"x":37029.960460867296,"y":22877.023877882995},"targetPosition":{"x":36709.925261896904,"y":23019.437319595327},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"055acccd-9f4a-4061-8450-2542728bea0d","savedPayload":{"position":{"x":36709.925261896904,"y":23019.437319595327},"targetPosition":{"x":36496.58422710869,"y":23312.033653946226},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"7c4c75d1-66a7-46fe-9d8b-f59127feb4fb","savedPayload":{"position":{"x":36408.12921121267,"y":22866.886167247496},"targetPosition":{"x":36322.16487414663,"y":23685.31432683232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"d4b86789-6853-4e39-87a2-c0db5ba83b57","savedPayload":{"position":{"x":36322.16487414663,"y":23685.31432683232},"targetPosition":{"x":36988.52935666488,"y":24169.481133182588},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 3","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.15","id":"3f765cfe-7c53-4864-9966-2189a50e67d3","savedPayload":{"position":{"x":35279.80885738757,"y":23368.56838345668},"targetPosition":{"x":35655.83443612933,"y":23201.19255471755},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.previous.movingAverage <= 15 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 5","id":"f3eb091e-10d2-42af-a3fd-0f292dd4facd","savedPayload":{"position":{"x":35912.426132011344,"y":22188.02473709919},"targetPosition":{"x":35916.25655923137,"y":22363.348447677257},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"6bc0645d-3c4b-4f51-b6d4-bf64a18c67ad","savedPayload":{"position":{"x":35916.25655923137,"y":22363.348447677257},"targetPosition":{"x":35920.423785958636,"y":22538.67684374441},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1f70959a-6225-418b-a8c0-9f0eb1b25959","savedPayload":{"position":{"x":35920.423785958636,"y":22538.67684374441},"targetPosition":{"x":35929.99672269212,"y":22900.68593225255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at08hs.percentageBandwidth.movingAverage <= 40 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 3","id":"7ea7b4d1-afca-4054-a879-f23b8af5f603","savedPayload":{"position":{"x":36332.45374018306,"y":22117.07062196842},"targetPosition":{"x":36269.12594360673,"y":22281.21403283719},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"46770ee9-f4a5-4954-bba6-a81a98ac170d","savedPayload":{"position":{"x":36269.12594360673,"y":22281.21403283719},"targetPosition":{"x":36143.284821448804,"y":22608.34684277255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.direction === \"Up\"","id":"ef2340f6-814f-4b36-bd7b-dda85145602f","savedPayload":{"position":{"x":36688.35895584827,"y":22167.168535872745},"targetPosition":{"x":36415.90984566528,"y":22387.88368661559},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"2bd3230e-6d75-40b8-a21d-ae996c0af609","savedPayload":{"position":{"x":36415.90984566528,"y":22387.88368661559},"targetPosition":{"x":36143.284821448804,"y":22608.34684277255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e6d5b689-a6aa-458d-812c-23aa107070f7","savedPayload":{"position":{"x":36143.284821448804,"y":22608.34684277255},"targetPosition":{"x":35929.99672269212,"y":22900.68593225255},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"a6666b42-09ab-4e1d-8496-68d75146df17","savedPayload":{"position":{"x":35741.788301955756,"y":22382.811190232384},"targetPosition":{"x":35655.83443612933,"y":23201.19255471755},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"5c382616-0be7-41ce-ba3b-be50f1171cc6","savedPayload":{"position":{"x":35655.83443612933,"y":23201.19255471755},"targetPosition":{"x":36322.16487414663,"y":23685.31432683232},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 4","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.movingAverage * 0.1","id":"f1cba918-c516-4a1b-89c6-0cd41c7c51ba","savedPayload":{"position":{"x":34613.50150082444,"y":22884.621673593494},"targetPosition":{"x":34989.484547146545,"y":22717.228144897},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at12hs.percentageBandwidth.previous.movingAverage <= 15 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >= 15","id":"7a388dfb-f8d5-4db5-b23f-5904cf537693","savedPayload":{"position":{"x":35345.54834782866,"y":21776.558779173272},"targetPosition":{"x":35349.45653262655,"y":21951.944394752234},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4612914d-6253-478a-9f65-2bb8d6b6b971","savedPayload":{"position":{"x":35349.45653262655,"y":21951.944394752234},"targetPosition":{"x":35353.67262658888,"y":22127.323998231765},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"404022c9-8d38-41e3-b54e-440efaf4043e","savedPayload":{"position":{"x":35353.67262658888,"y":22127.323998231765},"targetPosition":{"x":35363.33987740086,"y":22489.365901438257},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 70 || chart.at12hs.percentageBandwidth.movingAverage <= 40 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"1ed19248-d020-48cf-8108-720224f865f7","savedPayload":{"position":{"x":35765.689919450415,"y":21705.61672866174},"targetPosition":{"x":35702.37323616819,"y":21869.870719247032},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"id":"5ee85058-ba70-4e9f-8edd-b347db3e9d48","savedPayload":{"position":{"x":35702.37323616819,"y":21869.870719247032},"targetPosition":{"x":35576.559391603776,"y":22197.076412807688},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"7deb0164-b72a-4a5b-b530-d60ab9d17d64","savedPayload":{"position":{"x":36121.60989343169,"y":21755.73157600969},"targetPosition":{"x":35849.16698062161,"y":21976.524346573085},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"13fb342c-0335-40f9-b23d-3db153c33b16","savedPayload":{"position":{"x":35849.16698062161,"y":21976.524346573085},"targetPosition":{"x":35576.559391603776,"y":22197.076412807688},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e9d17467-711c-45f7-8a08-f34da5a9d4d5","savedPayload":{"position":{"x":35576.559391603776,"y":22197.076412807688},"targetPosition":{"x":35363.33987740086,"y":22489.365901438257},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"9963f1b1-f5aa-4cef-b355-1e496edfa9e9","savedPayload":{"position":{"x":35075.55883317493,"y":21898.966732555666},"targetPosition":{"x":34989.61658634904,"y":22717.254029968073},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"840184c3-e47a-4a42-a4d6-ceef67383a54","savedPayload":{"position":{"x":34989.447899426006,"y":22717.03424162746},"targetPosition":{"x":35655.83443612933,"y":23201.19255471755},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 5","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.75","id":"7548b47a-e78d-4679-8187-284a4f662261","savedPayload":{"position":{"x":33947.16212688377,"y":22400.829966740333},"targetPosition":{"x":34323.3327786729,"y":22233.159240527537},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 30 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=10","id":"ef8a4d0d-b3b8-437b-959a-c963f6c89fe3","savedPayload":{"position":{"x":34686.23734319014,"y":21373.562022389164},"targetPosition":{"x":34712.890400933815,"y":21546.67217851684},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"70e23a03-8532-4e6c-a5f7-0532578ee8c4","savedPayload":{"position":{"x":34712.890400933815,"y":21546.67217851684},"targetPosition":{"x":34740.132717953886,"y":21720.801015570323},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"7691f96d-3dd1-46fd-abd1-a0fa7c2e2457","savedPayload":{"position":{"x":34740.132717953886,"y":21720.801015570323},"targetPosition":{"x":34796.82413534809,"y":22077.91417113042},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 60 || chart.at12hs.percentageBandwidth.movingAverage <= 30 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"5aaa360b-617c-433f-bac0-360722424506","savedPayload":{"position":{"x":35260.930066821624,"y":21131.392613299886},"targetPosition":{"x":35135.5728173172,"y":21458.11081641969},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"90f6a6d2-4b9f-47f0-910b-61106d80b107","savedPayload":{"position":{"x":35135.5728173172,"y":21458.11081641969},"targetPosition":{"x":35010.10226864419,"y":21785.087224550778},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"a2607c85-5420-444e-9a11-4eedeb76745f","savedPayload":{"position":{"x":35554.276038028744,"y":21344.656009542323},"targetPosition":{"x":35282.35444746537,"y":21564.829089366543},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"3a75e5f5-3366-4c84-a15d-dc60119e2844","savedPayload":{"position":{"x":35282.35444746537,"y":21564.829089366543},"targetPosition":{"x":35010.10226864419,"y":21785.087224550778},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"3162a795-144e-419d-9323-d70546393310","savedPayload":{"position":{"x":35010.10226864419,"y":21785.087224550778},"targetPosition":{"x":34796.82413534809,"y":22077.91417113042},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"dd63d693-0e99-463a-a988-86287504d827","savedPayload":{"position":{"x":34409.77172681528,"y":21415.162105086772},"targetPosition":{"x":34323.32725745321,"y":22233.14268963072},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"65109409-9152-4cc4-93e7-c33301f6d28c","savedPayload":{"position":{"x":34323.33595340725,"y":22233.193086902567},"targetPosition":{"x":34989.61658634904,"y":22717.254029968073},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase 6","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation * 1.25","id":"c8800084-2dd6-44b5-b54a-e26a7dd3149a","savedPayload":{"position":{"x":33280.26062062725,"y":21916.058847959946},"targetPosition":{"x":33656.30735592492,"y":21748.61897320079},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{}}},"nextPhaseEvent":{"type":"Next Phase Event","situations":[{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.percentageBandwidth.previous.movingAverage <= 30 && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=10","id":"ea47f887-5722-46b5-9b97-ac7bedb3ee7b","savedPayload":{"position":{"x":34201.77769468176,"y":20604.192116809576},"targetPosition":{"x":34210.8857044323,"y":20953.745286913818},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"5fa32bce-6d0e-4b93-ab17-dba973cfad1a","savedPayload":{"position":{"x":34210.8857044323,"y":20953.745286913818},"targetPosition":{"x":34220.13697780592,"y":21303.74379169583},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"1825ef6b-4396-4635-a9da-3652cd510acd","savedPayload":{"position":{"x":34220.13697780592,"y":21303.74379169583},"targetPosition":{"x":34230.265895908764,"y":21666.041457149666},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Situation","name":"New Situation","conditions":[{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"(chart.at24hs.percentageBandwidth.movingAverage <= 50 || chart.at12hs.percentageBandwidth.movingAverage <= 17.5 ) && chart.at01hs.bollingerSubChannel.slope === \"Extreme\" && chart.at01hs.bollingerSubChannel.period >=5","id":"d19aeeec-1ed3-462b-8a55-b9806a17ad77","savedPayload":{"position":{"x":34777.155360963516,"y":20757.357517018118},"targetPosition":{"x":34610.00340944047,"y":21065.513546063103},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"b731c25b-4899-4198-98bc-a42c5e77cb14","savedPayload":{"position":{"x":34610.00340944047,"y":21065.513546063103},"targetPosition":{"x":34443.29351708951,"y":21373.376150149285},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":4,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"New Condition","javascriptCode":{"type":"Javascript Code","code":"chart.at24hs.bollingerChannel.previous.previous.direction === \"Up\"","id":"3a3e404a-06ca-4371-a5e4-f71d0ceab201","savedPayload":{"position":{"x":34987.71386018262,"y":20932.496636578537},"targetPosition":{"x":34715.44143342088,"y":21153.23638102526},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"188c7a7f-f9b7-4d4e-9147-065d1aa7a2f4","savedPayload":{"position":{"x":34715.44143342088,"y":21153.23638102526},"targetPosition":{"x":34443.29351708951,"y":21373.376150149285},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"869ee575-049f-4759-a498-79647b0ed1a4","savedPayload":{"position":{"x":34443.29351708951,"y":21373.376150149285},"targetPosition":{"x":34230.265895908764,"y":21666.041457149666},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"announcements":[],"id":"412e8505-aab3-4c3c-8799-dfddc8e879f2","savedPayload":{"position":{"x":33742.19649676662,"y":20930.31632524261},"targetPosition":{"x":33656.34260136011,"y":21748.59492729729},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"moveToPhaseEvents":[],"announcements":[],"id":"34563f7d-9c72-465b-8265-07302e098ff1","savedPayload":{"position":{"x":33657.17038592897,"y":21749.36153835545},"targetPosition":{"x":34323.32725745321,"y":22233.14268963072},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Phase","name":"New Phase","formula":{"type":"Formula","code":"chart.at01hs.bollingerBand.movingAverage - chart.at01hs.bollingerBand.deviation","id":"e63d85c1-3f43-4eee-96f2-3db5ae3db60e","savedPayload":{"position":{"x":32990.1264608507,"y":21264.6414477228},"targetPosition":{"x":33323.105667745454,"y":21506.49043771999},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"moveToPhaseEvents":[],"announcements":[],"id":"3f98a4e8-b586-4ffa-9630-86caec6508ff","savedPayload":{"position":{"x":33323.097425587905,"y":21506.467085729528},"targetPosition":{"x":33656.34260136011,"y":21748.59492729729},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"a3704633-a258-4bcf-9aef-063f91fe4729","savedPayload":{"position":{"x":37654.916260101934,"y":24653.743255781377},"targetPosition":{"x":38099.15732398546,"y":24976.757582524697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"announcements":[{"type":"Announcement","name":"Manage","code":"{ \n\"text\": \"Manage Stage: you will get the proper message when the trading session is run at a time frame lower than 1H.\",\n\"valueVariantion\": 2.5\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB Trade Management Status! Current TP, current price, and stop: \" + takeProfit.toFixed(0) + \", \" + chart.at01min.candle.close.toFixed(0) + \", \" + stopLoss.toFixed(0) + \".\"","id":"2c2a8de4-7aef-421d-b0c9-cbfcff444c84","savedPayload":{"position":{"x":38376.81204154982,"y":25097.712699873733},"targetPosition":{"x":38314.26395076143,"y":24699.928184312597},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"4264495f-73b8-4fb4-a41b-034b0727a9b0","savedPayload":{"position":{"x":38013.68104873196,"y":24434.85298322019},"targetPosition":{"x":38099.15732398546,"y":24976.757582524697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"e3e4ef8c-d328-417c-af43-1300f00f674f","savedPayload":{"position":{"x":38099.15732398546,"y":24976.757582524697},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"closeStage":{"type":"Close Stage","closeExecution":{"type":"Close Execution","id":"0b0c1936-fe07-43b9-80dd-8e8d7cb5e19d","savedPayload":{"position":{"x":38599.14393103092,"y":24239.61963606108},"targetPosition":{"x":38713.07429295899,"y":24777.04968466085},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"announcements":[{"type":"Announcement","name":"Close","code":"{ \n\"text\": \"Close Stage.\"\n}","formula":{"type":"Formula","name":"New Formula","code":"\"BBTB trade closed! P&L: \" + lastTradeProfitLoss.toFixed(2) * 100 + \"%. Balances: \" + balanceBaseAsset.toFixed(2) + \" BTC and \" + balanceQuotedAsset.toFixed(0) + \" USDT.\"","id":"d97442f9-5c13-4ad6-9151-878e6842fe57","savedPayload":{"position":{"x":38807.00785816407,"y":25105.620140331164},"targetPosition":{"x":39026.105344753065,"y":24767.32560373922},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"8df754d2-5c34-405b-b7ff-95c5f87083d2","savedPayload":{"position":{"x":39011.80785197776,"y":24317.005110283426},"targetPosition":{"x":38713.07429295899,"y":24777.04968466085},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":true,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"51a105b6-c1a1-4805-b0f0-a4895097f071","savedPayload":{"position":{"x":38713.07429295899,"y":24777.04968466085},"targetPosition":{"x":38543.28678959614,"y":25299.500783556694},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":2,"arrangementStyle":0},"uiObject":{"isRunning":false}}},"id":"3e5c4b18-a27e-4d12-a5d8-9bf44e970f1c","savedPayload":{"position":{"x":38543.28678959614,"y":25299.500783556694},"targetPosition":{"x":39642.04913415892,"y":25299.5},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}}],"id":"126a9ca3-6075-4cb2-a65f-0073c3a0bc00","savedPayload":{"position":{"x":39642.04913415892,"y":25299.5},"targetPosition":{"x":39642.04913415892,"y":25299.5},"floatingObject":{"isPinned":true,"isFrozen":false,"isCollapsed":true,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false,"shortcutKey":"w"}}} \ No newline at end of file