diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..82c487bdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ + +.vs/* +obj/* + +bin/* + +*.config +*.log +*.cache +node_modules/* + diff --git a/CandlePanel.js b/CandlePanel.js new file mode 100644 index 000000000..c223aaa74 --- /dev/null +++ b/CandlePanel.js @@ -0,0 +1,352 @@ + +function newMastersPlottersCandlesVolumesCandlesCandlePanel() { + + let thisObject = { + fitFunction: undefined, + container: undefined, + isVisible: true, + onRecordChange: onRecordChange, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + }; + + thisObject.container = newContainer(); + thisObject.container.initialize(); + thisObject.container.frame.containerName = "Candle"; + + let currentCandle; + let upDownButton + + return thisObject; + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.isVisible = undefined + + currentCandle = undefined + upDownButton.finalize() + upDownButton = undefined + } + + function initialize() { + + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL; + thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL; + + thisObject.container.frame.position.x = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.x - thisObject.container.frame.width * 2; + thisObject.container.frame.position.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y - thisObject.container.frame.height; + + upDownButton = newUpDownButton() + upDownButton.parentContainer = thisObject.container + upDownButton.container.frame.parentFrame = thisObject.container.frame + upDownButton.fitFunction = thisObject.fitFunction + upDownButton.initialize() + } + + function getContainer(point) { + if (thisObject.isVisible !== true) { return } + let container; + + container = upDownButton.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 onRecordChange(lastCurrentCandle) { + + currentCandle = lastCurrentCandle; + + } + + + function draw() { + if (thisObject.isVisible !== true) { return } + thisObject.container.frame.draw(false, false, true, thisObject.fitFunction); + + plotCurrentCandleInfo(); + + upDownButton.draw() + + } + + + function plotCurrentCandleInfo() { + + if (currentCandle === undefined) { return; } + if (currentCandle.innerCandle === 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; + + let candlePoint1 = { + x: X_AXIS - currentCandle.bodyWidth / 2, + y: Y_AXIS - currentCandle.bodyHeight / 2 + }; + + let candlePoint2 = { + x: X_AXIS + currentCandle.bodyWidth / 2, + y: Y_AXIS - currentCandle.bodyHeight / 2 + }; + + let candlePoint3 = { + x: X_AXIS + currentCandle.bodyWidth / 2, + y: Y_AXIS + currentCandle.bodyHeight / 2 + }; + + let candlePoint4 = { + x: X_AXIS - currentCandle.bodyWidth / 2, + y: Y_AXIS + currentCandle.bodyHeight / 2 + }; + + + let stickPoint1 = { + x: X_AXIS - currentCandle.stickWidth / 2, + y: candlePoint1.y - currentCandle.stickStart + }; + + let stickPoint2 = { + x: X_AXIS + currentCandle.stickWidth / 2, + y: candlePoint1.y - currentCandle.stickStart + }; + + let stickPoint3 = { + x: X_AXIS + currentCandle.stickWidth / 2, + y: candlePoint1.y - currentCandle.stickStart + currentCandle.stickHeight + }; + + let stickPoint4 = { + x: X_AXIS - currentCandle.stickWidth / 2, + y: candlePoint1.y - currentCandle.stickStart + currentCandle.stickHeight + }; + + + /* Extra bounderies due to constrained space */ + + const upperLimit = frameTitleHeight + frameBodyHeight * 0.15; + const downLimit = frameTitleHeight + frameBodyHeight * 0.85; + + if (candlePoint1.y < upperLimit) { + + candlePoint1.y = upperLimit; + candlePoint2.y = upperLimit; + + } + + if (stickPoint1.y < upperLimit) { + + stickPoint1.y = upperLimit; + stickPoint2.y = upperLimit; + + } + + if (candlePoint3.y < upperLimit) { + + candlePoint3.y = upperLimit; + candlePoint4.y = upperLimit; + + } + + if (stickPoint3.y < upperLimit) { + + stickPoint3.y = upperLimit; + stickPoint4.y = upperLimit; + + } + + if (candlePoint1.y > downLimit) { + + candlePoint1.y = downLimit; + candlePoint2.y = downLimit; + + } + + if (stickPoint1.y > downLimit) { + + stickPoint1.y = downLimit; + stickPoint2.y = downLimit; + + } + + if (candlePoint3.y > downLimit) { + + candlePoint3.y = downLimit; + candlePoint4.y = downLimit; + + } + + if (stickPoint3.y > downLimit) { + + stickPoint3.y = downLimit; + stickPoint4.y = downLimit; + + } + + const leftLimit = thisObject.container.frame.width * 0.42; + const rightLimit = thisObject.container.frame.width * 0.58; + + if (candlePoint1.x < leftLimit) { + + candlePoint1.x = leftLimit; + candlePoint4.x = leftLimit; + + } + + if (stickPoint1.x < leftLimit) { + + stickPoint1.x = leftLimit; + stickPoint4.x = leftLimit; + + } + + if (candlePoint4.x < leftLimit) { + + candlePoint4.x = leftLimit; + candlePoint1.x = leftLimit; + + } + + if (stickPoint4.x < leftLimit) { + + stickPoint4.x = leftLimit; + stickPoint1.x = leftLimit; + + } + + if (candlePoint2.x > rightLimit) { + + candlePoint2.x = rightLimit; + candlePoint3.x = rightLimit; + + } + + if (stickPoint2.x > rightLimit) { + + stickPoint2.x = rightLimit; + stickPoint3.x = rightLimit; + + } + + if (candlePoint3.x > rightLimit) { + + candlePoint3.x = rightLimit; + candlePoint2.x = rightLimit; + + } + + if (stickPoint3.x > rightLimit) { + + stickPoint3.x = rightLimit; + stickPoint2.x = rightLimit; + + } + + + candlePoint1 = thisObject.container.frame.frameThisPoint(candlePoint1); + candlePoint2 = thisObject.container.frame.frameThisPoint(candlePoint2); + candlePoint3 = thisObject.container.frame.frameThisPoint(candlePoint3); + candlePoint4 = thisObject.container.frame.frameThisPoint(candlePoint4); + + candlePoint1 = thisObject.fitFunction(candlePoint1) + candlePoint2 = thisObject.fitFunction(candlePoint2) + candlePoint3 = thisObject.fitFunction(candlePoint3) + candlePoint4 = thisObject.fitFunction(candlePoint4) + + stickPoint1 = thisObject.container.frame.frameThisPoint(stickPoint1); + stickPoint2 = thisObject.container.frame.frameThisPoint(stickPoint2); + stickPoint3 = thisObject.container.frame.frameThisPoint(stickPoint3); + stickPoint4 = thisObject.container.frame.frameThisPoint(stickPoint4); + + stickPoint1 = thisObject.fitFunction(stickPoint1) + stickPoint2 = thisObject.fitFunction(stickPoint2) + stickPoint3 = thisObject.fitFunction(stickPoint3) + stickPoint4 = thisObject.fitFunction(stickPoint4) + + browserCanvasContext.beginPath(); + + browserCanvasContext.moveTo(stickPoint1.x, stickPoint1.y); + browserCanvasContext.lineTo(stickPoint2.x, stickPoint2.y); + browserCanvasContext.lineTo(stickPoint3.x, stickPoint3.y); + browserCanvasContext.lineTo(stickPoint4.x, stickPoint4.y); + + browserCanvasContext.closePath(); + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK + ', 1)'; + browserCanvasContext.fill(); + + browserCanvasContext.lineWidth = 1; + browserCanvasContext.stroke(); + + browserCanvasContext.beginPath(); + + browserCanvasContext.moveTo(candlePoint1.x, candlePoint1.y); + browserCanvasContext.lineTo(candlePoint2.x, candlePoint2.y); + browserCanvasContext.lineTo(candlePoint3.x, candlePoint3.y); + browserCanvasContext.lineTo(candlePoint4.x, candlePoint4.y); + + browserCanvasContext.closePath(); + + const OPACITY = '1'; + + if (currentCandle.innerCandle.direction === 'up') { browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', ' + OPACITY + ')'; } + if (currentCandle.innerCandle.direction === 'down') { browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RED + ', ' + OPACITY + ')'; } + if (currentCandle.innerCandle.direction === 'side') { browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.DARK + ', ' + OPACITY + ')'; } + + if (currentCandle.innerCandle.direction === 'up') { browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.GREEN + ', ' + OPACITY + ')'; } + if (currentCandle.innerCandle.direction === 'down') { browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + OPACITY + ')'; } + if (currentCandle.innerCandle.direction === 'side') { browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK + ', ' + OPACITY + ')'; } + + browserCanvasContext.fill(); + + browserCanvasContext.lineWidth = 1; + browserCanvasContext.stroke(); + + /* put the labels with the candles values */ + + let y; + + UI.projects.superalgos.utilities.drawPrint.printLabel('Max', undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.05, '0.60', undefined, undefined, 'Center', thisObject.container, thisObject.fitFunction); + UI.projects.superalgos.utilities.drawPrint.printLabel(currentCandle.innerCandle.max, undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.10, '1.00', 15, undefined, 'Center', thisObject.container, thisObject.fitFunction); + + UI.projects.superalgos.utilities.drawPrint.printLabel('Min', undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.95, '0.60', undefined, undefined, 'Center', thisObject.container, thisObject.fitFunction); + UI.projects.superalgos.utilities.drawPrint.printLabel(currentCandle.innerCandle.min, undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.90, '1.00', 15, undefined, 'Center', thisObject.container, thisObject.fitFunction); + + y = Y_AXIS - currentCandle.bodyHeight / 2; + + if (y < frameTitleHeight + frameBodyHeight * 0.15) { y = frameTitleHeight + frameBodyHeight * 0.15; } + if (y > frameTitleHeight + frameBodyHeight * 0.80) { y = frameTitleHeight + frameBodyHeight * 0.80; } + + UI.projects.superalgos.utilities.drawPrint.printLabel('Open', undefined, undefined, X_AXIS * 2 / 5, y, '0.60', undefined, undefined, 'Center', thisObject.container, thisObject.fitFunction); + UI.projects.superalgos.utilities.drawPrint.printLabel(currentCandle.innerCandle.open, undefined, undefined, X_AXIS * 2 / 5, y + frameBodyHeight * 0.05, '1.00', 15, undefined, 'Center', thisObject.container, thisObject.fitFunction); + + y = Y_AXIS + currentCandle.bodyHeight / 2; + + if (y < frameTitleHeight + frameBodyHeight * 0.15) { y = frameTitleHeight + frameBodyHeight * 0.15; } + if (y > frameTitleHeight + frameBodyHeight * 0.80) { y = frameTitleHeight + frameBodyHeight * 0.80; } + + UI.projects.superalgos.utilities.drawPrint.printLabel('Close', undefined, undefined, X_AXIS * 7 / 5, y, '0.60', undefined, undefined, 'Center', thisObject.container, thisObject.fitFunction); + UI.projects.superalgos.utilities.drawPrint.printLabel(currentCandle.innerCandle.close, undefined, undefined, X_AXIS * 7 / 5, y + frameBodyHeight * 0.05, '1.00', 15, undefined, 'Center', thisObject.container, thisObject.fitFunction); + + } +} + + diff --git a/Candles.js b/Candles.js new file mode 100644 index 000000000..46aba26b6 --- /dev/null +++ b/Candles.js @@ -0,0 +1,810 @@ +function newMastersPlottersCandlesVolumesCandles() { + + const MODULE_NAME = "Candles Plotter"; + const ERROR_LOG = true; + const INTENSIVE_LOG = false; + const logger = newWebDebugLog(); + logger.fileName = MODULE_NAME; + + let thisObject = { + initialize: initialize, + finalize: finalize, + container: undefined, + fitFunction: undefined, + getContainer: getContainer, + setTimeFrame: setTimeFrame, + setDatetime: setDatetime, + setCoordinateSystem: setCoordinateSystem, + draw: draw, + onDailyFileLoaded: onDailyFileLoaded, + currentCandle: undefined + }; + + /* this is part of the module template */ + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + let coordinateSystem + + let timeFrame; // This will hold the current Time Frame the user is at. + let datetime; // This will hold the current Datetime the user is at. + + let marketFile; // This is the current Market File being plotted. + let fileCursor; // This is the current File Cursor being used to retrieve Daily Files. + + let marketFiles; // This object will provide the different Market Files at different Time Frames. + let dailyFiles; // This object will provide the different File Cursors at different Time Frames. + + /* these are module specific variables: */ + + let candles = []; // Here we keep the candles to be ploted every time the Draw() function is called by the AAWebPlatform. + + let onMouseOverEventSuscriptionId + let zoomChangedEventSubscriptionId + let offsetChangedEventSubscriptionId + let dragFinishedEventSubscriptionId + let dimmensionsChangedEventSubscriptionId + let marketFilesUpdatedEventSubscriptionId + let dailyFilesUpdatedEventSubscriptionId + let scaleChangedEventSubscriptionId + + let userPositionDate + return thisObject; + + function finalize() { + try { + + /* Stop listening to the necesary events. */ + thisObject.container.eventHandler.stopListening(onMouseOverEventSuscriptionId) + UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.stopListening(zoomChangedEventSubscriptionId); + UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.stopListening(offsetChangedEventSubscriptionId); + canvas.eventHandler.stopListening(dragFinishedEventSubscriptionId); + thisObject.container.eventHandler.stopListening(dimmensionsChangedEventSubscriptionId) + marketFiles.eventHandler.stopListening(marketFilesUpdatedEventSubscriptionId); + dailyFiles.eventHandler.stopListening(dailyFilesUpdatedEventSubscriptionId); + + /* Destroyd References */ + + marketFiles = undefined; + dailyFiles = undefined; + + datetime = undefined; + timeFrame = undefined; + + marketFile = undefined; + fileCursor = undefined; + + thisObject.fitFunction = undefined + + finalizeCoordinateSystem() + coordinateSystem = undefined + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] finalize -> err = " + err.stack); } + } + } + + function initialize(pStorage, pDatetime, pTimeFrame, pCoordinateSystem, callBackFunction) { + + try { + + /* Store the information received. */ + + marketFiles = pStorage.marketFiles[0]; + dailyFiles = pStorage.dailyFiles[0]; + + datetime = pDatetime; + timeFrame = pTimeFrame; + coordinateSystem = pCoordinateSystem + initializeCoordinateSystem() + + /* We need a Market File in order to calculate the Y scale, since this scale depends on actual data. */ + + marketFile = marketFiles.getFile(ONE_DAY_IN_MILISECONDS); // This file is the one processed faster. + + /* Now we set the right files according to current Period. */ + + marketFile = marketFiles.getFile(pTimeFrame); + fileCursor = dailyFiles.getFileCursor(pTimeFrame); + + /* Listen to the necesary events. */ + + zoomChangedEventSubscriptionId = UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.listenToEvent("Zoom Changed", onViewportZoomChanged); + offsetChangedEventSubscriptionId = UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.listenToEvent("Position Changed", onViewportPositionChanged); + dragFinishedEventSubscriptionId = canvas.eventHandler.listenToEvent("Drag Finished", onDragFinished); + marketFilesUpdatedEventSubscriptionId = marketFiles.eventHandler.listenToEvent("Files Updated", onMarketFilesUpdated); + dailyFilesUpdatedEventSubscriptionId = dailyFiles.eventHandler.listenToEvent("Files Updated", onDailyFilesUpdated); + onMouseOverEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + + /* Get ready for plotting. */ + + recalculate(); + + dimmensionsChangedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('Dimmensions Changed', function () { + recalculate(); + }) + + callBackFunction(); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] initialize -> err = " + err.stack); } + } + } + + function initializeCoordinateSystem() { + scaleChangedEventSubscriptionId = coordinateSystem.eventHandler.listenToEvent('Scale Changed', onScaleChanged) + } + + function finalizeCoordinateSystem() { + coordinateSystem.eventHandler.stopListening(scaleChangedEventSubscriptionId) + } + + function onScaleChanged() { + recalculate(); + } + + function onMouseOver(event) { + let userPosition = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(event, thisObject.container, coordinateSystem) + userPositionDate = userPosition.valueOf() + } + + function getContainer(point) { + + try { + + let container; + + /* First we check if this point is inside this space. */ + + if (thisObject.container.frame.isThisPointHere(point) === true) { + + return thisObject.container; + + } else { + + /* This point does not belong to this space. */ + + return undefined; + } + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] getContainer -> err = " + err.stack); } + } + } + + function onMarketFilesUpdated() { + try { + let newMarketFile = marketFiles.getFile(timeFrame); + if (newMarketFile !== undefined) { + marketFile = newMarketFile; + recalculate(); + } + } catch (err) { + if (ERROR_LOG === true) { logger.write("[ERROR] onMarketFilesUpdated -> err = " + err.stack); } + } + } + + function onDailyFilesUpdated() { + try { + let newFileCursor = dailyFiles.getFileCursor(timeFrame); + if (newFileCursor !== undefined) { + fileCursor = newFileCursor; + recalculate(); + } + } catch (err) { + if (ERROR_LOG === true) { logger.write("[ERROR] onDailyFilesUpdated -> err = " + err.stack); } + } + } + + function setTimeFrame(pTimeFrame) { + + try { + if (timeFrame !== pTimeFrame) { + timeFrame = pTimeFrame; + + if (timeFrame >= _1_HOUR_IN_MILISECONDS) { + let newMarketFile = marketFiles.getFile(pTimeFrame); + if (newMarketFile !== undefined) { + marketFile = newMarketFile; + recalculate(); + } + + } else { + let newFileCursor = dailyFiles.getFileCursor(pTimeFrame); + if (newFileCursor !== undefined) { + fileCursor = newFileCursor; + recalculate(); + } + } + } + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] setTimeFrame -> err = " + err.stack); } + } + } + + function setDatetime(pDatetime) { + datetime = pDatetime; + } + + function setCoordinateSystem(pCoordinateSystem) { + finalizeCoordinateSystem() + coordinateSystem = pCoordinateSystem + initializeCoordinateSystem() + } + + function onDailyFileLoaded(event) { + + try { + + if (event.currentValue === event.totalValue) { + + /* This happens only when all of the files in the cursor have been loaded. */ + + recalculate(); + + } + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] onDailyFileLoaded -> err = " + err.stack); } + } + } + + function draw() { + + try { + + if (INTENSIVE_LOG === true) { logger.write("[INFO] draw -> Entering function."); } + + thisObject.container.frame.draw(); + + return plotChart(); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] draw -> err = " + err.stack); } + } + } + + function recalculate() { + + try { + + if (timeFrame >= _1_HOUR_IN_MILISECONDS) { + + recalculateUsingMarketFiles(); + + } else { + + recalculateUsingDailyFiles(); + + } + + thisObject.container.eventHandler.raiseEvent("Candles Changed", candles); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] recalculate -> err = " + err.stack); } + } + } + + function recalculateUsingDailyFiles() { + + try { + + if (fileCursor === undefined) { return; } // We need to wait + + if (fileCursor.files.size === 0) { return; } // We need to wait until there are files in the cursor + + let daysOnSides = getSideDays(timeFrame); + + let leftDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem); + let rightDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight, thisObject.container, coordinateSystem); + + let dateDiff = rightDate.valueOf() - leftDate.valueOf(); + + let farLeftDate = new Date(leftDate.valueOf() - dateDiff * 1.5); + let farRightDate = new Date(rightDate.valueOf() + dateDiff * 1.5); + + let currentDate = new Date(farLeftDate.valueOf()); + + candles = []; + + while (currentDate.valueOf() <= farRightDate.valueOf() + ONE_DAY_IN_MILISECONDS) { + + let stringDate = currentDate.getUTCFullYear() + '-' + pad(currentDate.getUTCMonth() + 1, 2) + '-' + pad(currentDate.getUTCDate(), 2); + + let dailyFile = fileCursor.files.get(stringDate); + + if (dailyFile !== undefined) { + + for (let i = 0; i < dailyFile.length; i++) { + + let candle = { + open: undefined, + close: undefined, + min: 10000000000000, + max: 0, + begin: undefined, + end: undefined, + direction: undefined + }; + + candle.min = dailyFile[i][0]; + candle.max = dailyFile[i][1]; + + candle.open = dailyFile[i][2]; + candle.close = dailyFile[i][3]; + + candle.begin = dailyFile[i][4]; + candle.end = dailyFile[i][5]; + + if (candle.open > candle.close) { candle.direction = 'down'; } + if (candle.open < candle.close) { candle.direction = 'up'; } + if (candle.open === candle.close) { candle.direction = 'side'; } + + /* Contributing to Auto-Scale*/ + if (i === 0) { + coordinateSystem.reportXValue(candle.begin) + } + if (i === dailyFile.length - 1) { + coordinateSystem.reportXValue(candle.end) + } + + if ( + (candle.begin >= farLeftDate.valueOf() && candle.end <= farRightDate.valueOf()) && + (candle.begin >= coordinateSystem.min.x && candle.end <= coordinateSystem.max.x) + ) { + + candles.push(candle); + + if (datetime.valueOf() >= candle.begin && datetime.valueOf() <= candle.end) { + + thisObject.currentCandle = candle; + thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentCandle); + + } + } + } + } + + currentDate = new Date(currentDate.valueOf() + ONE_DAY_IN_MILISECONDS); + } + + /* Lests check if all the visible screen is going to be covered by candles. */ + + let lowerEnd = leftDate.valueOf(); + let upperEnd = rightDate.valueOf(); + + if (candles.length > 0) { + if (candles[0].begin > lowerEnd || candles[candles.length - 1].end < upperEnd) { + setTimeout(recalculate, 2000); + } + } + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] recalculateUsingDailyFiles -> err = " + err.stack); } + } + } + + function recalculateUsingMarketFiles() { + + try { + + if (marketFile === undefined) { return; } // Initialization not complete yet. + + let daysOnSides = getSideDays(timeFrame); + + let leftDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem); + let rightDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight, thisObject.container, coordinateSystem); + + let dateDiff = rightDate.valueOf() - leftDate.valueOf(); + + leftDate = new Date(leftDate.valueOf() - dateDiff * 1.5); + rightDate = new Date(rightDate.valueOf() + dateDiff * 1.5); + + candles = []; + + for (let i = 0; i < marketFile.length; i++) { + + let candle = { + open: undefined, + close: undefined, + min: 10000000000000, + max: 0, + begin: undefined, + end: undefined, + direction: undefined + }; + + candle.min = marketFile[i][0]; + candle.max = marketFile[i][1]; + + candle.open = marketFile[i][2]; + candle.close = marketFile[i][3]; + + candle.begin = marketFile[i][4]; + candle.end = marketFile[i][5]; + + if (candle.open > candle.close) { candle.direction = 'down'; } + if (candle.open < candle.close) { candle.direction = 'up'; } + if (candle.open === candle.close) { candle.direction = 'side'; } + + /* Contributing to Auto-Scale*/ + if (i === 0) { + coordinateSystem.reportXValue(candle.begin) + } + if (i === marketFile.length - 1) { + coordinateSystem.reportXValue(candle.end) + } + + if ( + (candle.begin >= leftDate.valueOf() && candle.end <= rightDate.valueOf()) && + (candle.begin >= coordinateSystem.min.x && candle.end <= coordinateSystem.max.x) + ) { + + candles.push(candle); + + if (datetime.valueOf() >= candle.begin && datetime.valueOf() <= candle.end) { + + thisObject.currentCandle = candle; + thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentCandle); + + } + } + } + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] recalculateUsingMarketFiles -> err = " + err.stack); } + } + } + + function plotChart() { + + try { + let elementsPlotted = 0 + let lowResolution = false + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_PLOTTING_IN_LOW_RESOLUTION) { + if (candles.length > 100) { + lowResolution = true + } + } + + if (candles.length > 0) { + + /* On screen candles */ + let onScreenCandles = [] + let mouseCandle + + /* Now we calculate all the points. */ + for (let i = 0; i < candles.length; i++) { + + candle = candles[i]; + + candle.candlePoint1 = { + x: candle.begin + timeFrame / 7 * 1.5, + y: candle.open + }; + + candle.candlePoint2 = { + x: candle.begin + timeFrame / 7 * 5.5, + y: candle.open + }; + + candle.candlePoint3 = { + x: candle.begin + timeFrame / 7 * 5.5, + y: candle.close + }; + + candle.candlePoint4 = { + x: candle.begin + timeFrame / 7 * 1.5, + y: candle.close + }; + + candle.candlePoint1 = coordinateSystem.transformThisPoint(candle.candlePoint1); + candle.candlePoint2 = coordinateSystem.transformThisPoint(candle.candlePoint2); + candle.candlePoint3 = coordinateSystem.transformThisPoint(candle.candlePoint3); + candle.candlePoint4 = coordinateSystem.transformThisPoint(candle.candlePoint4); + + candle.candlePoint1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.candlePoint1, thisObject.container); + candle.candlePoint2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.candlePoint2, thisObject.container); + candle.candlePoint3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.candlePoint3, thisObject.container); + candle.candlePoint4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.candlePoint4, thisObject.container); + + /* Skip the first candle with zero at open */ + if (candle.open === 0) { continue } + + if (candle.candlePoint2.x < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.x || candle.candlePoint1.x > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.x) { + continue + } + + addToOnScreenCandles(candle) + + candle.stickPoint1 = { + x: candle.begin + timeFrame / 7 * 3.2, + y: candle.max + }; + + candle.stickPoint2 = { + x: candle.begin + timeFrame / 7 * 3.8, + y: candle.max + }; + + candle.stickPoint3 = { + x: candle.begin + timeFrame / 7 * 3.8, + y: candle.min + }; + + candle.stickPoint4 = { + x: candle.begin + timeFrame / 7 * 3.2, + y: candle.min + }; + + candle.stickPoint1 = coordinateSystem.transformThisPoint(candle.stickPoint1); + candle.stickPoint2 = coordinateSystem.transformThisPoint(candle.stickPoint2); + candle.stickPoint3 = coordinateSystem.transformThisPoint(candle.stickPoint3); + candle.stickPoint4 = coordinateSystem.transformThisPoint(candle.stickPoint4); + + candle.stickPoint1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.stickPoint1, thisObject.container); + candle.stickPoint2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.stickPoint2, thisObject.container); + candle.stickPoint3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.stickPoint3, thisObject.container); + candle.stickPoint4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(candle.stickPoint4, thisObject.container); + + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_PACKING_OBJECTS_AT_THE_BOTTOM_OR_TOP_OF_VIEWPORT) { + let diffA = candle.stickPoint3.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y + if (diffA > 0) { + candle.candlePoint1.y = candle.candlePoint1.y - diffA + candle.candlePoint2.y = candle.candlePoint2.y - diffA + candle.candlePoint3.y = candle.candlePoint3.y - diffA + candle.candlePoint4.y = candle.candlePoint4.y - diffA + + candle.stickPoint1.y = candle.stickPoint1.y - diffA + candle.stickPoint2.y = candle.stickPoint2.y - diffA + candle.stickPoint3.y = candle.stickPoint3.y - diffA + candle.stickPoint4.y = candle.stickPoint4.y - diffA + } + + let diffB = candle.stickPoint1.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y + if (diffB < 0) { + candle.candlePoint1.y = candle.candlePoint1.y - diffB + candle.candlePoint2.y = candle.candlePoint2.y - diffB + candle.candlePoint3.y = candle.candlePoint3.y - diffB + candle.candlePoint4.y = candle.candlePoint4.y - diffB + + candle.stickPoint1.y = candle.stickPoint1.y - diffB + candle.stickPoint2.y = candle.stickPoint2.y - diffB + candle.stickPoint3.y = candle.stickPoint3.y - diffB + candle.stickPoint4.y = candle.stickPoint4.y - diffB + } + } + + candle.candlePoint1 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.candlePoint1); + candle.candlePoint2 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.candlePoint2); + candle.candlePoint3 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.candlePoint3); + candle.candlePoint4 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.candlePoint4); + + candle.candlePoint1 = thisObject.fitFunction(candle.candlePoint1); + candle.candlePoint2 = thisObject.fitFunction(candle.candlePoint2); + candle.candlePoint3 = thisObject.fitFunction(candle.candlePoint3); + candle.candlePoint4 = thisObject.fitFunction(candle.candlePoint4); + + candle.stickPoint1 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.stickPoint1); + candle.stickPoint2 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.stickPoint2); + candle.stickPoint3 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.stickPoint3); + candle.stickPoint4 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(candle.stickPoint4); + + candle.stickPoint1 = thisObject.fitFunction(candle.stickPoint1); + candle.stickPoint2 = thisObject.fitFunction(candle.stickPoint2); + candle.stickPoint3 = thisObject.fitFunction(candle.stickPoint3); + candle.stickPoint4 = thisObject.fitFunction(candle.stickPoint4); + } + + + + function addToOnScreenCandles(candle) { + + /* Contributing to Auto-Scale*/ + coordinateSystem.reportYValue(candle.max) + coordinateSystem.reportYValue(candle.min) + + + //if (onScreenCandles.length < 15000) { + onScreenCandles.push(candle) + //} + + if (userPositionDate >= candle.begin && userPositionDate <= candle.end) { + mouseCandle = candle + } + } + + /* We draw the sticks */ + browserCanvasContext.beginPath(); + + for (let i = 0; i < onScreenCandles.length; i++) { + + candle = onScreenCandles[i]; + + browserCanvasContext.moveTo(candle.stickPoint1.x, candle.stickPoint1.y); + browserCanvasContext.lineTo(candle.stickPoint2.x, candle.stickPoint2.y); + browserCanvasContext.lineTo(candle.stickPoint3.x, candle.stickPoint3.y); + browserCanvasContext.lineTo(candle.stickPoint4.x, candle.stickPoint4.y); + } + + browserCanvasContext.closePath(); + + if (lowResolution === false) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK + ', 1)'; + browserCanvasContext.fill(); + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.LIGHT + ', 1)'; + browserCanvasContext.lineWidth = 1; + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke(); + + } + + /* The stick at the mouse candle */ + if (mouseCandle) { + browserCanvasContext.beginPath(); + + candle = mouseCandle + + browserCanvasContext.moveTo(candle.stickPoint1.x, candle.stickPoint1.y); + browserCanvasContext.lineTo(candle.stickPoint2.x, candle.stickPoint2.y); + browserCanvasContext.lineTo(candle.stickPoint3.x, candle.stickPoint3.y); + browserCanvasContext.lineTo(candle.stickPoint4.x, candle.stickPoint4.y); + + browserCanvasContext.closePath(); + + if (lowResolution === false) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK + ', 1)'; + browserCanvasContext.fill(); + } + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 1)'; + browserCanvasContext.lineWidth = 1; + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke(); + } + + + /* We draw the candles. */ + drawCandlesByDirection('up') + drawCandlesByDirection('down') + drawCandlesByDirection('side') + + function drawCandlesByDirection(direction) { + + browserCanvasContext.beginPath(); + + for (let i = 0; i < onScreenCandles.length; i++) { + + candle = onScreenCandles[i]; + + if (candle.direction !== direction) { continue } + + browserCanvasContext.moveTo(candle.candlePoint1.x, candle.candlePoint1.y); + browserCanvasContext.lineTo(candle.candlePoint2.x, candle.candlePoint2.y); + browserCanvasContext.lineTo(candle.candlePoint3.x, candle.candlePoint3.y); + browserCanvasContext.lineTo(candle.candlePoint4.x, candle.candlePoint4.y); + browserCanvasContext.lineTo(candle.candlePoint1.x, candle.candlePoint1.y); + + elementsPlotted++ + } + + browserCanvasContext.closePath(); + + if (lowResolution === false) { + + if (direction === 'up') { browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.GREEN + ', 1)'; } + if (direction === 'down') { browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', 1)'; } + if (direction === 'side') { browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK + ', 1)'; } + + browserCanvasContext.fill(); + } + + if (direction === 'up') { browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', 1)'; } + if (direction === 'down') { browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RED + ', 1)'; } + if (direction === 'side') { browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.DARK + ', 1)'; } + + browserCanvasContext.lineWidth = 1; + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke(); + + } + + /* Draw mouse candel and Raise Event. */ + if (mouseCandle) { + + /* highlight the current candle */ + + browserCanvasContext.beginPath(); + + candle = mouseCandle + + browserCanvasContext.moveTo(candle.candlePoint1.x, candle.candlePoint1.y); + browserCanvasContext.lineTo(candle.candlePoint2.x, candle.candlePoint2.y); + browserCanvasContext.lineTo(candle.candlePoint3.x, candle.candlePoint3.y); + browserCanvasContext.lineTo(candle.candlePoint4.x, candle.candlePoint4.y); + + browserCanvasContext.closePath(); + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 1)'; // Current candle accroding to time + + browserCanvasContext.fill(); + + let currentCandle = { + bodyWidth: candle.candlePoint2.x - candle.candlePoint1.x, + bodyHeight: candle.candlePoint3.y - candle.candlePoint2.y, + stickHeight: candle.stickPoint4.y - candle.stickPoint2.y, + stickWidth: candle.stickPoint2.x - candle.stickPoint1.x, + stickStart: candle.candlePoint2.y - candle.stickPoint2.y, + period: timeFrame, + innerCandle: candle + }; + thisObject.container.eventHandler.raiseEvent("Current Record Changed", currentCandle); + } + } + return elementsPlotted + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] plotChart -> err = " + err.stack); } + } + } + + + function onViewportZoomChanged(event) { + + try { + + recalculate(); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] onViewportZoomChanged -> err = " + err.stack); } + } + } + + function onViewportPositionChanged(event) { + + try { + + if (event !== undefined) { + if (event.recalculate === true) { + recalculate() + return + } + } + if (Math.random() * 100 > 95) { + recalculate() + }; + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] onViewportPositionChanged -> err = " + err.stack); } + } + } + + function onDragFinished() { + + try { + + recalculate(); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] onDragFinished -> err = " + err.stack); } + } + } +} + + + + + diff --git a/Candles.png b/Candles.png new file mode 100644 index 000000000..f1e093cd5 Binary files /dev/null and b/Candles.png differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..10e295746 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Financial Beings License + +Collective Copyright by the Advanced Algos Community + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software including without limitation the rights to use, copy, modify, +merge, publish, distribute and sublicense copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following +conditions: + + 1. Using, copying, modifying, publishing, distributing and sublicensing rights + are restricted to the Advanced Algos Platform, meaning the rights granted can + only be exercised through the Advanced Algos Service. + + 2. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Plotters-Candles-Volumes.sln b/Plotters-Candles-Volumes.sln new file mode 100644 index 000000000..661fcc076 --- /dev/null +++ b/Plotters-Candles-Volumes.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Project", "Project.njsproj", "{096B35AD-F86F-47B9-A99F-04CA93397DA8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {096B35AD-F86F-47B9-A99F-04CA93397DA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {096B35AD-F86F-47B9-A99F-04CA93397DA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {096B35AD-F86F-47B9-A99F-04CA93397DA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {096B35AD-F86F-47B9-A99F-04CA93397DA8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E75EDC9D-66A7-4C54-8EA1-5ADC148CACA8} + EndGlobalSection +EndGlobal diff --git a/Project.njsproj b/Project.njsproj new file mode 100644 index 000000000..f8e8bdc92 --- /dev/null +++ b/Project.njsproj @@ -0,0 +1,72 @@ + + + + Debug + 2.0 + {096b35ad-f86f-47b9-a99f-04ca93397da8} + + ShowAllFiles + Candles.js + . + . + {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + Project + + + + + + + + + Code + + + + + + + + + + + + + + False + True + 0 + / + http://localhost:48022/ + False + True + http://localhost:1337 + False + + + + + + + CurrentPage + True + False + False + False + + + + + + + + + False + False + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..5aa302b7d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# AAOlivia-Plotter diff --git a/VolumePanel.js b/VolumePanel.js new file mode 100644 index 000000000..9a1608a65 --- /dev/null +++ b/VolumePanel.js @@ -0,0 +1,256 @@ + +function newMastersPlottersCandlesVolumesVolumesVolumePanel() { + + let thisObject = { + fitFunction: undefined, + container: undefined, + isVisible: true, + onRecordChange: onRecordChange, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + }; + + thisObject.container = newContainer(); + thisObject.container.initialize(); + thisObject.container.frame.containerName = "Volume"; + + let currentVolume; + let upDownButton + + return thisObject; + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.isVisible = undefined + + currentVolume = undefined + upDownButton.finalize() + upDownButton = undefined + } + + function initialize() { + + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL; + thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL; + + thisObject.container.frame.position.x = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.x - thisObject.container.frame.width; + thisObject.container.frame.position.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y - thisObject.container.frame.height; + + upDownButton = newUpDownButton() + upDownButton.parentContainer = thisObject.container + upDownButton.container.frame.parentFrame = thisObject.container.frame + upDownButton.fitFunction = thisObject.fitFunction + upDownButton.initialize() + } + + function getContainer(point) { + if (thisObject.isVisible !== true) { return } + let container; + + container = upDownButton.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 onRecordChange(lastCurrentVolume) { + + currentVolume = lastCurrentVolume; + + } + + + function draw() { + if (thisObject.isVisible !== true) { return } + thisObject.container.frame.draw(false, false, true, thisObject.fitFunction); + + plotCurrentVolumeInfo(); + + upDownButton.draw() + } + + function plotCurrentVolumeInfo() { + + if (currentVolume === undefined) { return; } + if (currentVolume.innerVolumeBar === 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_BUY = frameTitleHeight + frameBodyHeight * 0.85; + const Y_AXIS_SELL = frameTitleHeight + frameBodyHeight * 0.15; + + let buyVolumePoint1 = { + x: X_AXIS - currentVolume.buyInfo.baseWidth / 2, + y: Y_AXIS_BUY + }; + + let buyVolumePoint2 = { + x: X_AXIS - currentVolume.buyInfo.topWidth / 2, + y: Y_AXIS_BUY + currentVolume.buyInfo.height + }; + + let buyVolumePoint3 = { + x: X_AXIS + currentVolume.buyInfo.topWidth / 2, + y: Y_AXIS_BUY + currentVolume.buyInfo.height + }; + + let buyVolumePoint4 = { + x: X_AXIS + currentVolume.buyInfo.baseWidth / 2, + y: Y_AXIS_BUY + }; + + /* Extra bounderies due to constrained space */ + + const upperLimit = frameTitleHeight + frameBodyHeight * 0.15; + const downLimit = frameTitleHeight + frameBodyHeight * 0.85; + + const leftLimit = thisObject.container.frame.width * 0.40; + const rightLimit = thisObject.container.frame.width * 0.60; + + if (buyVolumePoint1.y < downLimit) { buyVolumePoint1.y = downLimit; } + if (buyVolumePoint4.y < downLimit) { buyVolumePoint4.y = downLimit; } + + if (buyVolumePoint2.y < upperLimit) { buyVolumePoint2.y = upperLimit; } + if (buyVolumePoint3.y < upperLimit) { buyVolumePoint3.y = upperLimit; } + + if (buyVolumePoint1.x < leftLimit) { buyVolumePoint1.x = leftLimit; } + if (buyVolumePoint2.x < leftLimit) { buyVolumePoint2.x = leftLimit; } + + if (buyVolumePoint3.x > rightLimit) { buyVolumePoint3.x = rightLimit; } + if (buyVolumePoint4.x > rightLimit) { buyVolumePoint4.x = rightLimit; } + + + + buyVolumePoint1 = thisObject.container.frame.fitIntoFrame(buyVolumePoint1); + buyVolumePoint2 = thisObject.container.frame.fitIntoFrame(buyVolumePoint2); + buyVolumePoint3 = thisObject.container.frame.fitIntoFrame(buyVolumePoint3); + buyVolumePoint4 = thisObject.container.frame.fitIntoFrame(buyVolumePoint4); + + buyVolumePoint1 = thisObject.container.frame.frameThisPoint(buyVolumePoint1); + buyVolumePoint2 = thisObject.container.frame.frameThisPoint(buyVolumePoint2); + buyVolumePoint3 = thisObject.container.frame.frameThisPoint(buyVolumePoint3); + buyVolumePoint4 = thisObject.container.frame.frameThisPoint(buyVolumePoint4); + + buyVolumePoint1 = thisObject.fitFunction(buyVolumePoint1) + buyVolumePoint2 = thisObject.fitFunction(buyVolumePoint2) + buyVolumePoint3 = thisObject.fitFunction(buyVolumePoint3) + buyVolumePoint4 = thisObject.fitFunction(buyVolumePoint4) + + let sellVolumePoint1 = { + x: X_AXIS - currentVolume.buyInfo.baseWidth / 2, + y: Y_AXIS_SELL + }; + + let sellVolumePoint2 = { + x: X_AXIS - currentVolume.buyInfo.topWidth / 2, + y: Y_AXIS_SELL + currentVolume.sellInfo.height + }; + + let sellVolumePoint3 = { + x: X_AXIS + currentVolume.buyInfo.topWidth / 2, + y: Y_AXIS_SELL + currentVolume.sellInfo.height + }; + + let sellVolumePoint4 = { + x: X_AXIS + currentVolume.buyInfo.baseWidth / 2, + y: Y_AXIS_SELL + }; + + if (sellVolumePoint1.y < upperLimit) { sellVolumePoint1.y = upperLimit; } + if (sellVolumePoint4.y < upperLimit) { sellVolumePoint4.y = upperLimit; } + + if (sellVolumePoint2.y > downLimit) { sellVolumePoint2.y = downLimit; } + if (sellVolumePoint3.y > downLimit) { sellVolumePoint3.y = downLimit; } + + if (sellVolumePoint1.x < leftLimit) { sellVolumePoint1.x = leftLimit; } + if (sellVolumePoint2.x < leftLimit) { sellVolumePoint2.x = leftLimit; } + + if (sellVolumePoint3.x > rightLimit) { sellVolumePoint3.x = rightLimit; } + if (sellVolumePoint4.x > rightLimit) { sellVolumePoint4.x = rightLimit; } + + sellVolumePoint1 = thisObject.container.frame.fitIntoFrame(sellVolumePoint1); + sellVolumePoint2 = thisObject.container.frame.fitIntoFrame(sellVolumePoint2); + sellVolumePoint3 = thisObject.container.frame.fitIntoFrame(sellVolumePoint3); + sellVolumePoint4 = thisObject.container.frame.fitIntoFrame(sellVolumePoint4); + + sellVolumePoint1 = thisObject.container.frame.frameThisPoint(sellVolumePoint1); + sellVolumePoint2 = thisObject.container.frame.frameThisPoint(sellVolumePoint2); + sellVolumePoint3 = thisObject.container.frame.frameThisPoint(sellVolumePoint3); + sellVolumePoint4 = thisObject.container.frame.frameThisPoint(sellVolumePoint4); + + sellVolumePoint1 = thisObject.fitFunction(sellVolumePoint1) + sellVolumePoint2 = thisObject.fitFunction(sellVolumePoint2) + sellVolumePoint3 = thisObject.fitFunction(sellVolumePoint3) + sellVolumePoint4 = thisObject.fitFunction(sellVolumePoint4) + + + + const OPACITY = '0.40'; + + browserCanvasContext.beginPath(); + + browserCanvasContext.moveTo(buyVolumePoint1.x, buyVolumePoint1.y); + browserCanvasContext.lineTo(buyVolumePoint2.x, buyVolumePoint2.y); + browserCanvasContext.lineTo(buyVolumePoint3.x, buyVolumePoint3.y); + browserCanvasContext.lineTo(buyVolumePoint4.x, buyVolumePoint4.y); + + browserCanvasContext.closePath(); + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', ' + OPACITY + ')'; + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.GREEN + ', ' + OPACITY + ')'; + + browserCanvasContext.fill(); + + browserCanvasContext.lineWidth = 1; + browserCanvasContext.stroke(); + + + browserCanvasContext.beginPath(); + + browserCanvasContext.moveTo(sellVolumePoint1.x, sellVolumePoint1.y); + browserCanvasContext.lineTo(sellVolumePoint2.x, sellVolumePoint2.y); + browserCanvasContext.lineTo(sellVolumePoint3.x, sellVolumePoint3.y); + browserCanvasContext.lineTo(sellVolumePoint4.x, sellVolumePoint4.y); + + browserCanvasContext.closePath(); + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RED + ', ' + OPACITY + ')'; + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + OPACITY + ')'; + + browserCanvasContext.fill(); + + browserCanvasContext.lineWidth = 1; + browserCanvasContext.stroke(); + + /* put the labels with the volumes values */ + + UI.projects.superalgos.utilities.drawPrint.printLabel('Buy Volume', undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.95, '0.60', undefined, undefined, 'Center', thisObject.container, thisObject.fitFunction); + UI.projects.superalgos.utilities.drawPrint.printLabel(currentVolume.innerVolumeBar.amountBuy, undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.90, '1.00', 15, undefined, 'Center', thisObject.container, thisObject.fitFunction); + + UI.projects.superalgos.utilities.drawPrint.printLabel('Sell Volume', undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.05, '0.60', undefined, undefined, 'Center', thisObject.container, thisObject.fitFunction); + UI.projects.superalgos.utilities.drawPrint.printLabel(currentVolume.innerVolumeBar.amountSell, undefined, undefined, X_AXIS, frameTitleHeight + frameBodyHeight * 0.10, '1.00', 15, undefined, 'Center', thisObject.container, thisObject.fitFunction); + + } + + +} + diff --git a/Volumes.js b/Volumes.js new file mode 100644 index 000000000..cf5691eee --- /dev/null +++ b/Volumes.js @@ -0,0 +1,755 @@ +function newMastersPlottersCandlesVolumesVolumes() { + + const MODULE_NAME = "Volumes Plotter"; + const ERROR_LOG = true; + const INTENSIVE_LOG = false; + const logger = newWebDebugLog(); + logger.fileName = MODULE_NAME; + + let thisObject = { + + /* Events declared outside the plotter. */ + + onDailyFileLoaded: onDailyFileLoaded, + + // Main functions and properties. + + initialize: initialize, + finalize: finalize, + container: undefined, + fitFunction: undefined, + getContainer: getContainer, + setTimeFrame: setTimeFrame, + setDatetime: setDatetime, + setCoordinateSystem: setCoordinateSystem, + recalculateScale: recalculateScale, + draw: draw + }; + + /* this is part of the module template */ + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + let coordinateSystem + + let timeFrame; // This will hold the current Time Frame the user is at. + let datetime; // This will hold the current Datetime the user is at. + + let marketFile; // This is the current Market File being plotted. + let fileCursor; // This is the current File Cursor being used to retrieve Daily Files. + + let marketFiles; // This object will provide the different Market Files at different Time Frames. + let dailyFiles; // This object will provide the different File Cursors at different Time Frames. + + let scaleFile; // This file is used to calculate the scale. + + /* these are module specific variables: */ + + let volumes = []; // Here we keep the volumes to be ploted every time the Draw() function is called by the AAWebPlatform. + + let onMouseOverEventSuscriptionId + let zoomChangedEventSubscriptionId + let offsetChangedEventSubscriptionId + let dragFinishedEventSubscriptionId + let dimmensionsChangedEventSubscriptionId + let marketFilesUpdatedEventSubscriptionId + let dailyFilesUpdatedEventSubscriptionId + let scaleChangedEventSubscriptionId + + let userPositionDate + return thisObject; + + function finalize() { + try { + + /* Stop listening to the necesary events. */ + thisObject.container.eventHandler.stopListening(onMouseOverEventSuscriptionId) + UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.stopListening(zoomChangedEventSubscriptionId); + UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.stopListening(offsetChangedEventSubscriptionId); + canvas.eventHandler.stopListening(dragFinishedEventSubscriptionId); + thisObject.container.eventHandler.stopListening(dimmensionsChangedEventSubscriptionId) + marketFiles.eventHandler.stopListening(marketFilesUpdatedEventSubscriptionId); + dailyFiles.eventHandler.stopListening(dailyFilesUpdatedEventSubscriptionId); + + /* Destroyd References */ + + marketFiles = undefined; + dailyFiles = undefined; + + datetime = undefined; + timeFrame = undefined; + + marketFile = undefined; + fileCursor = undefined; + + thisObject.fitFunction = undefined + + finalizeCoordinateSystem() + coordinateSystem = undefined + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] finalize -> err = " + err.stack); } + } + } + + function initialize(pStorage, pDatetime, pTimeFrame, pCoordinateSystem, callBackFunction) { + + try { + + /* Store the information received. */ + + marketFiles = pStorage.marketFiles[0]; + dailyFiles = pStorage.dailyFiles[0]; + + datetime = pDatetime; + timeFrame = pTimeFrame; + coordinateSystem = pCoordinateSystem + initializeCoordinateSystem() + + /* We need a Market File in order to calculate the Y scale, since this scale depends on actual data. */ + + scaleFile = marketFiles.getFile(ONE_DAY_IN_MILISECONDS); // This file is the one processed faster. + + /* Now we set the right files according to current Period. */ + + marketFile = marketFiles.getFile(pTimeFrame); + fileCursor = dailyFiles.getFileCursor(pTimeFrame); + + /* Listen to the necesary events. */ + + zoomChangedEventSubscriptionId = UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.listenToEvent("Zoom Changed", onViewportZoomChanged); + offsetChangedEventSubscriptionId = UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.listenToEvent("Position Changed", onViewportPositionChanged); + dragFinishedEventSubscriptionId = canvas.eventHandler.listenToEvent("Drag Finished", onDragFinished); + marketFilesUpdatedEventSubscriptionId = marketFiles.eventHandler.listenToEvent("Files Updated", onMarketFilesUpdated); + dailyFilesUpdatedEventSubscriptionId = dailyFiles.eventHandler.listenToEvent("Files Updated", onDailyFilesUpdated); + onMouseOverEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + + /* Get ready for plotting. */ + + recalculate(); + + dimmensionsChangedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('Dimmensions Changed', function () { + recalculate(); + }) + + callBackFunction(); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] initialize -> err = " + err.stack); } + } + } + + function initializeCoordinateSystem() { + scaleChangedEventSubscriptionId = coordinateSystem.eventHandler.listenToEvent('Scale Changed', onScaleChanged) + } + + function finalizeCoordinateSystem() { + coordinateSystem.eventHandler.stopListening(scaleChangedEventSubscriptionId) + } + + function onScaleChanged() { + recalculate(); + } + + function onMouseOver(event) { + let userPosition = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(event, thisObject.container, coordinateSystem) + userPositionDate = userPosition.valueOf() + } + + function recalculateScale() { + recalculate(); + } + + function getContainer(point) { + + try { + + let container; + + /* First we check if this point is inside this space. */ + + if (thisObject.container.frame.isThisPointHere(point) === true) { + + return thisObject.container; + + } else { + + /* This point does not belong to this space. */ + + return undefined; + } + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] getContainer -> err = " + err.stack); } + } + } + + function onMarketFilesUpdated() { + try { + let newMarketFile = marketFiles.getFile(timeFrame); + if (newMarketFile !== undefined) { + marketFile = newMarketFile; + recalculate(); + } + } catch (err) { + if (ERROR_LOG === true) { logger.write("[ERROR] onMarketFilesUpdated -> err = " + err.stack); } + } + } + + function onDailyFilesUpdated() { + try { + let newFileCursor = dailyFiles.getFileCursor(timeFrame); + if (newFileCursor !== undefined) { + fileCursor = newFileCursor; + recalculate(); + } + } catch (err) { + if (ERROR_LOG === true) { logger.write("[ERROR] onDailyFilesUpdated -> err = " + err.stack); } + } + } + + function setTimeFrame(pTimeFrame) { + + try { + + if (timeFrame !== pTimeFrame) { + + timeFrame = pTimeFrame; + + if (timeFrame >= _1_HOUR_IN_MILISECONDS) { + + let newMarketFile = marketFiles.getFile(pTimeFrame); + + if (newMarketFile !== undefined) { + + marketFile = newMarketFile; + recalculate(); + } + + } else { + + let newFileCursor = dailyFiles.getFileCursor(pTimeFrame); + + if (newFileCursor !== undefined) { + + fileCursor = newFileCursor; + recalculate(); + } + } + } + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] setTimeFrame -> err = " + err.stack); } + } + } + + function setDatetime(pDatetime) { + datetime = pDatetime; + } + + function setCoordinateSystem(pCoordinateSystem) { + finalizeCoordinateSystem() + coordinateSystem = pCoordinateSystem + initializeCoordinateSystem() + } + + function onDailyFileLoaded(event) { + + if (event.currentValue === event.totalValue) { + /* This happens only when all of the files in the cursor have been loaded. */ + recalculate(); + } + } + + function draw() { + + try { + + if (INTENSIVE_LOG === true) { logger.write("[INFO] draw -> Entering function."); } + + thisObject.container.frame.draw(); + + plotChart(); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] draw -> err = " + err.stack); } + } + } + + function recalculate() { + if (timeFrame >= _1_HOUR_IN_MILISECONDS) { + recalculateUsingMarketFiles(); + } else { + recalculateUsingDailyFiles(); + } + thisObject.container.eventHandler.raiseEvent("Volumes Changed", volumes); + } + + function recalculateUsingDailyFiles() { + + try { + + if (fileCursor === undefined) { return; } // We need to wait + + if (fileCursor.files.size === 0) { return; } // We need to wait until there are files in the cursor + + let leftDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem); + let rightDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight, thisObject.container, coordinateSystem); + + let dateDiff = rightDate.valueOf() - leftDate.valueOf(); + + let farLeftDate = new Date(leftDate.valueOf() - dateDiff * 1.5); + let farRightDate = new Date(rightDate.valueOf() + dateDiff * 1.5); + + let currentDate = new Date(farLeftDate.valueOf()); + + volumes = []; + + while (currentDate.valueOf() <= farRightDate.valueOf() + ONE_DAY_IN_MILISECONDS) { + + let stringDate = currentDate.getUTCFullYear() + '-' + pad(currentDate.getUTCMonth() + 1, 2) + '-' + pad(currentDate.getUTCDate(), 2); + + let dailyFile = fileCursor.files.get(stringDate); + + if (dailyFile !== undefined) { + + for (let i = 0; i < dailyFile.length; i++) { + + let volume = { + amountBuy: 0, + amountSell: 0, + begin: undefined, + end: undefined + }; + + volume.amountBuy = dailyFile[i][0]; + volume.amountSell = dailyFile[i][1]; + + volume.begin = dailyFile[i][2]; + volume.end = dailyFile[i][3]; + + if ( + (volume.begin >= farLeftDate.valueOf() && volume.end <= farRightDate.valueOf()) && + (volume.begin >= coordinateSystem.min.x && volume.end <= coordinateSystem.max.x) + ) { + + volumes.push(volume); + + if (datetime.valueOf() >= volume.begin && datetime.valueOf() <= volume.end) { + + thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentCandle); + + } + } + } + } + + currentDate = new Date(currentDate.valueOf() + ONE_DAY_IN_MILISECONDS); + } + + /* Lests check if all the visible screen is going to be covered by volumes. */ + + let lowerEnd = leftDate.valueOf(); + let upperEnd = rightDate.valueOf(); + + if (volumes.length > 0) { + + if (volumes[0].begin > lowerEnd || volumes[volumes.length - 1].end < upperEnd) { + + setTimeout(recalculate, 2000); + + //console.log("File missing while calculating volumes, scheduling a recalculation in 2 seconds."); + + } + } + + //console.log("Olivia > recalculateUsingDailyFiles > total volumes generated : " + volumes.length); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] recalculateUsingDailyFiles -> err = " + err.stack); } + } + } + + function recalculateUsingMarketFiles() { + + try { + + if (marketFile === undefined) { return; } // Initialization not complete yet. + + let leftDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem); + let rightDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight, thisObject.container, coordinateSystem); + + let dateDiff = rightDate.valueOf() - leftDate.valueOf(); + + leftDate = new Date(leftDate.valueOf() - dateDiff * 1.5); + rightDate = new Date(rightDate.valueOf() + dateDiff * 1.5); + + volumes = []; + + for (let i = 0; i < marketFile.length; i++) { + + let volume = { + amountBuy: 0, + amountSell: 0, + begin: undefined, + end: undefined + }; + + volume.amountBuy = marketFile[i][0]; + volume.amountSell = marketFile[i][1]; + + volume.begin = marketFile[i][2]; + volume.end = marketFile[i][3]; + + if ( + (volume.begin >= leftDate.valueOf() && volume.end <= rightDate.valueOf()) && + (volume.begin >= coordinateSystem.min.x && volume.end <= coordinateSystem.max.x) + ) { + + volumes.push(volume); + + if (datetime.valueOf() >= volume.begin && datetime.valueOf() <= volume.end) { + + thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentCandle); + + } + } + } + + //console.log("Olivia > recalculateUsingMarketFiles > total volumes generated : " + volumes.length); + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] recalculateUsingMarketFiles -> err = " + err.stack); } + } + } + + function plotChart() { + + try { + + let lowResolution = false + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_PLOTTING_IN_LOW_RESOLUTION) { + if (volumes.length > 100) { + lowResolution = true + } + } + + if (volumes.length > 0) { + + /* Now we calculate and plot the volumes */ + + let visibleHeight = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y; + + let frameCorner1 = { + x: 0, + y: 0 + }; + + let frameCorner2 = { + x: thisObject.container.frame.width, + y: thisObject.container.frame.height + }; + + + /* Now the transformations. */ + + frameCorner1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(frameCorner1, thisObject.container.frame.container); + frameCorner2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(frameCorner2, thisObject.container.frame.container); + + let frameHeightInViewPort = frameCorner2.y - frameCorner1.y; + + if (volumes.length > 0) { + + for (let i = 0; i < volumes.length; i++) { + + volume = volumes[i]; + + let volumePointA1; + let volumePointA2; + let volumePointA3; + let volumePointA4; + + function calculateBuys(plot, height) { + + volumePointA1 = { + x: volume.begin + timeFrame / 7 * 2, + y: 0 + }; + + volumePointA2 = { + x: volume.begin + timeFrame / 7 * 2, + y: volume.amountBuy + }; + + volumePointA3 = { + x: volume.begin + timeFrame / 7 * 5, + y: volume.amountBuy + }; + + volumePointA4 = { + x: volume.begin + timeFrame / 7 * 5, + y: 0 + }; + + volumePointA1 = plot.transformThisPoint(volumePointA1); + volumePointA2 = plot.transformThisPoint(volumePointA2); + volumePointA3 = plot.transformThisPoint(volumePointA3); + volumePointA4 = plot.transformThisPoint(volumePointA4); + + volumePointA1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointA1, thisObject.container); + volumePointA2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointA2, thisObject.container); + volumePointA3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointA3, thisObject.container); + volumePointA4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointA4, thisObject.container); + + let baseIncrement = (volumePointA3.x - volumePointA1.x) * WIDHTER_VOLUME_BAR_BASE_FACTOR; + + volumePointA1.x = volumePointA1.x - baseIncrement; + volumePointA4.x = volumePointA4.x + baseIncrement; + + if (volumePointA4.x < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.x || volumePointA1.x > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.x) { + return false; + } + + return true; + + } + + if (calculateBuys(coordinateSystem, thisObject.container.frame.height) === false) { continue; } // We try to see if it fits in the visible area. + + let diffA = volumePointA1.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y + if (diffA > 0) { + volumePointA1.y = volumePointA1.y - diffA + volumePointA2.y = volumePointA2.y - diffA + volumePointA3.y = volumePointA3.y - diffA + volumePointA4.y = volumePointA4.y - diffA + } + + let volumePointB1; + let volumePointB2; + let volumePointB3; + let volumePointB4; + + function calculateSells(plot, height) { + + volumePointB1 = { + x: volume.begin + timeFrame / 7 * 2, + y: height + }; + + volumePointB2 = { + x: volume.begin + timeFrame / 7 * 2, + y: height - volume.amountSell + }; + + volumePointB3 = { + x: volume.begin + timeFrame / 7 * 5, + y: height - volume.amountSell + }; + + volumePointB4 = { + x: volume.begin + timeFrame / 7 * 5, + y: height + }; + + volumePointB1 = plot.transformThisPoint2(volumePointB1); + volumePointB2 = plot.transformThisPoint2(volumePointB2); + volumePointB3 = plot.transformThisPoint2(volumePointB3); + volumePointB4 = plot.transformThisPoint2(volumePointB4); + + volumePointB1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointB1, thisObject.container); + volumePointB2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointB2, thisObject.container); + volumePointB3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointB3, thisObject.container); + volumePointB4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(volumePointB4, thisObject.container); + } + + calculateSells(coordinateSystem, thisObject.container.frame.height); // We try to see if it fits in the visible area. + + let diffB = volumePointB1.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y + if (diffB < 0) { + volumePointB1.y = volumePointB1.y - diffB + volumePointB2.y = volumePointB2.y - diffB + volumePointB3.y = volumePointB3.y - diffB + volumePointB4.y = volumePointB4.y - diffB + } + + /* We put a wider base */ + + let baseIncrement = (volumePointB3.x - volumePointB1.x) * WIDHTER_VOLUME_BAR_BASE_FACTOR; + + volumePointB1.x = volumePointB1.x - baseIncrement; + volumePointB4.x = volumePointB4.x + baseIncrement; + + /* We put a less wider top */ + + let baseDencrement = (volumePointA3.x - volumePointA2.x) * LESS_WIDHTER_VOLUME_BAR_TOP_FACTOR; + + volumePointA2.x = volumePointA2.x + baseDencrement; + volumePointA3.x = volumePointA3.x - baseDencrement; + + volumePointB2.x = volumePointB2.x + baseDencrement; + volumePointB3.x = volumePointB3.x - baseDencrement; + + + /* Everything must fit within the visible area */ + + volumePointA1 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointA1); + volumePointA2 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointA2); + volumePointA3 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointA3); + volumePointA4 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointA4); + + volumePointB1 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointB1); + volumePointB2 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointB2); + volumePointB3 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointB3); + volumePointB4 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(volumePointB4); + + volumePointA1 = thisObject.fitFunction(volumePointA1); + volumePointA2 = thisObject.fitFunction(volumePointA2); + volumePointA3 = thisObject.fitFunction(volumePointA3); + volumePointA4 = thisObject.fitFunction(volumePointA4); + + volumePointB1 = thisObject.fitFunction(volumePointB1); + volumePointB2 = thisObject.fitFunction(volumePointB2); + volumePointB3 = thisObject.fitFunction(volumePointB3); + volumePointB4 = thisObject.fitFunction(volumePointB4); + + /* Everything must fit within the Frame. We know that that is equivalent to say that each bar con not be higher than the base of the opposite . */ + + if (volumePointA2.y < volumePointB1.y) { + + volumePointA2.y = volumePointB1.y; + volumePointA3.y = volumePointB1.y; + + } + + if (volumePointB2.y > volumePointA1.y) { + + volumePointB2.y = volumePointA1.y; + volumePointB3.y = volumePointA1.y; + + } + + /* Now the drawing of the volume bars*/ + + browserCanvasContext.beginPath(); + + browserCanvasContext.moveTo(volumePointA1.x, volumePointA1.y); + browserCanvasContext.lineTo(volumePointA2.x, volumePointA2.y); + browserCanvasContext.lineTo(volumePointA3.x, volumePointA3.y); + browserCanvasContext.lineTo(volumePointA4.x, volumePointA4.y); + + browserCanvasContext.closePath(); + + if (lowResolution === false) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.GREEN + ', 0.40)'; + + if (userPositionDate >= volume.begin && userPositionDate <= volume.end) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 0.40)'; // Current bar accroding to time + } + } + + browserCanvasContext.fill(); + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', 0.40)'; + browserCanvasContext.lineWidth = 1; + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke(); + + browserCanvasContext.beginPath(); + + browserCanvasContext.moveTo(volumePointB1.x, volumePointB1.y); + browserCanvasContext.lineTo(volumePointB2.x, volumePointB2.y); + browserCanvasContext.lineTo(volumePointB3.x, volumePointB3.y); + browserCanvasContext.lineTo(volumePointB4.x, volumePointB4.y); + + browserCanvasContext.closePath(); + + if (lowResolution === false) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', 0.40)'; + + if (userPositionDate >= volume.begin && userPositionDate <= volume.end) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 0.40)'; // Current volume accroding to time + } + + browserCanvasContext.fill(); + } + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RED + ', 0.40)'; + + browserCanvasContext.lineWidth = 1; + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke(); + + if (userPositionDate >= volume.begin && userPositionDate <= volume.end) { + + let buyInfo = { + baseWidth: volumePointA4.x - volumePointA1.x, + topWidth: volumePointA3.x - volumePointA2.x, + height: volumePointA2.y - volumePointA1.y + }; + + let sellInfo = { + baseWidth: volumePointB4.x - volumePointB1.x, + topWidth: volumePointB3.x - volumePointB2.x, + height: volumePointB2.y - volumePointB1.y + }; + + let currentVolume = { + buyInfo: buyInfo, + sellInfo: sellInfo, + period: timeFrame, + innerVolumeBar: volume + }; + + thisObject.container.eventHandler.raiseEvent("Current Record Changed", currentVolume); + + } + /* Contributing to Auto-Scale*/ + coordinateSystem.reportYValue(volume.amountBuy * 5) + coordinateSystem.reportYValue(volume.amountSell * 5) + coordinateSystem.reportYValue(0) + } + } + } + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] plotChart -> err = " + err.stack); } + } + } + + function onViewportZoomChanged(event) { + recalculate() + } + + function onViewportPositionChanged(event) { + + try { + if (event !== undefined) { + if (event.recalculate === true) { + recalculate(); + return + } + } + if (Math.random() * 100 > 95) { + recalculate(); + }; + + } catch (err) { + + if (ERROR_LOG === true) { logger.write("[ERROR] onViewportPositionChanged -> err = " + err.stack); } + } + } + + function onDragFinished() { + recalculate(); + } +} + + diff --git a/Volumes.png b/Volumes.png new file mode 100644 index 000000000..aaea183a6 Binary files /dev/null and b/Volumes.png differ diff --git a/this.plotter.config.json b/this.plotter.config.json new file mode 100644 index 000000000..5ac90a9dc --- /dev/null +++ b/this.plotter.config.json @@ -0,0 +1,33 @@ +{ + "displayName": "Plotters Candles & Volumes", + "codeName": "PlottersCandlesVolumes", + "dataMine": "Masters", + "modules": [ + { + "codeName": "Candles", + "moduleName": "Candles", + "description": "Plots traditional red and green candles.", + "profilePicture": "Candles.png", + "panels": [ + { + "codeName": "CandlePanel", + "moduleName": "CandlePanel", + "event": "Current Record Changed" + } + ] + }, + { + "codeName": "Volumes", + "description": "Plots volumes separated in buy and sell.", + "moduleName": "Volumes", + "profilePicture": "Volumes.png", + "panels": [ + { + "codeName": "VolumePanel", + "moduleName": "VolumePanel", + "event": "Current Record Changed" + } + ] + } + ] +} \ No newline at end of file