commit e393232c7a115bea290f26b95d257342ae0ed9fd Author: Luis Fernando Molina Date: Tue Feb 27 15:50:04 2018 +0100 Initial commit 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/.vs/AAMariam-Plotter/v15/.suo b/.vs/AAMariam-Plotter/v15/.suo new file mode 100644 index 000000000..d1501bee5 Binary files /dev/null and b/.vs/AAMariam-Plotter/v15/.suo differ diff --git a/.vs/AAOlivia-Plotter/v15/.suo b/.vs/AAOlivia-Plotter/v15/.suo new file mode 100644 index 000000000..781901c30 Binary files /dev/null and b/.vs/AAOlivia-Plotter/v15/.suo differ diff --git a/AAMariam-Plotter.njsproj b/AAMariam-Plotter.njsproj new file mode 100644 index 000000000..762c10990 --- /dev/null +++ b/AAMariam-Plotter.njsproj @@ -0,0 +1,67 @@ + + + + Debug + 2.0 + {096b35ad-f86f-47b9-a99f-04ca93397da8} + + ShowAllFiles + History.js + . + . + {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + AAMariam-Plotter + + + + + + + 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/AAMariam-Plotter.sln b/AAMariam-Plotter.sln new file mode 100644 index 000000000..83a9e865a --- /dev/null +++ b/AAMariam-Plotter.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}") = "AAMariam-Plotter", "AAMariam-Plotter.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/Details.js b/Details.js new file mode 100644 index 000000000..ae1961bf6 --- /dev/null +++ b/Details.js @@ -0,0 +1,762 @@ +function newAAMastersAAOliviaVolumes() { + + let thisObject = { + + // Main functions and properties. + + initialize: initialize, + container: undefined, + getContainer: getContainer, + setTimePeriod: setTimePeriod, + setDatetime: setDatetime, + draw: draw, + + // Secondary functions and properties. + + onLayerStatusChanged: onLayerStatusChanged + }; + + /* 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 layerStatus = 'off'; // Since the moduleis a layer, it must handle the different possible layer status. + + let plotArea = newPlotArea(); // Needed to be able to plot on the timeline, otherwise not. + let plotAreaFrame = newPlotArea(); // This chart uses this extra object. + + 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 marketFile; // This is the current Market File being plotted. + let fileCursor; // This is the current File Cursor being used to retrieve Daily Files. + + let fileCache; // This object will provide the different Market Files at different Time Periods. + let fileCursorCache; // This object will provide the different File Cursors at different Time Periods. + + /* 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. + + return thisObject; + + function initialize(pExchange, pMarket, pDatetime, pTimePeriod, pLayerStatus, callBackFunction) { + + let cursorCacheInProgress = false; + let finaleStepsInProgress = false; + + datetime = pDatetime; + timePeriod = pTimePeriod; + + fileCache = newFileCache(); + fileCache.initialize("AAMasters", "AAOlivia", "Market Volumes", "Market Volumes", pExchange, pMarket, onFileReady); + + function onFileReady() { + + let newMarketFile = fileCache.getFile(ONE_DAY_IN_MILISECONDS); + + if (newMarketFile !== undefined) { + + marketFile = newMarketFile; + + initializeFileCursorCache(); + + } + } + + function initializeFileCursorCache() { + + if (cursorCacheInProgress === false) { + + cursorCacheInProgress = true; + + fileCursorCache = newFileCursorCache(); + fileCursorCache.initialize("AAMasters", "AAOlivia", "Daily Volumes", "Daily Volumes", pExchange, pMarket, pDatetime, pTimePeriod, onFileCursorReady); + + } + } + + function onFileCursorReady() { + + recalculate(); + + let newFileCursor = fileCursorCache.getFileCursor(pTimePeriod); + + if (newFileCursor !== undefined) { // if the file ready is the one we need then it and we dont have it yet, then we will continue here. + + let stringDate = datetime.getUTCFullYear() + '-' + pad(datetime.getUTCMonth() + 1, 2) + '-' + pad(datetime.getUTCDate(), 2); + + let dailyFile = newFileCursor.files.get(stringDate); + + if (dailyFile !== undefined) { + + if (finaleStepsInProgress === false) { + + finaleStepsInProgress = true; + + fileCursor = newFileCursor; + + layerStatus = pLayerStatus; + + recalculateScaleX(); + recalculate(); + recalculateScaleY(); + + viewPort.eventHandler.listenToEvent("Zoom Changed", onZoomChanged); + canvas.eventHandler.listenToEvent("Drag Finished", onDragFinished); + + callBackFunction(); + + } + } + } + + } + + } + + function getContainer(point) { + + if (layerStatus !== 'on') { return; } + + 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 setTimePeriod(pTimePeriod) { + + timePeriod = pTimePeriod; + + if (timePeriod >= _1_HOUR_IN_MILISECONDS) { + + let newMarketFile = fileCache.getFile(pTimePeriod); + + if (newMarketFile !== undefined) { + + marketFile = newMarketFile; + + recalculate(); + + } + + } else { + + fileCursorCache.setTimePeriod(pTimePeriod); + + fileCursorCache.setDatetime(datetime); + + let newFileCursor = fileCursorCache.getFileCursor(pTimePeriod); + + if (newFileCursor !== undefined) { + + fileCursor = newFileCursor; + + recalculate(); + + } + } + + if (timePeriod === _1_HOUR_IN_MILISECONDS) { + + fileCursorCache.setTimePeriod(pTimePeriod); + + fileCursorCache.setDatetime(datetime); + + } + + } + + function setDatetime(newDatetime) { + + /* If there is a change in the day, then we take some actions, otherwise, we dont. */ + + let currentDate = Math.trunc(datetime.valueOf() / ONE_DAY_IN_MILISECONDS); + let newDate = Math.trunc(newDatetime.valueOf() / ONE_DAY_IN_MILISECONDS); + + datetime = newDatetime; + + if (currentDate !== newDate) { + + if (timePeriod <= _1_HOUR_IN_MILISECONDS) { + + fileCursorCache.setDatetime(newDatetime); + + } + } + } + + function draw() { + + if (layerStatus !== 'on') { return; } + + this.container.frame.draw(); + + if (timePeriod < _1_HOUR_IN_MILISECONDS) { + + if (Math.random() * 1000 > 995) { + + recalculateScaleX(); + recalculate(); + recalculateScaleY(); + + } + } + + plotChart(); + + } + + function recalculate() { + + if (layerStatus === 'off') { return; } + + if (timePeriod >= _1_HOUR_IN_MILISECONDS) { + + recalculateUsingMarketFiles(); + + } else { + + recalculateUsingDailyFiles(); + + } + + thisObject.container.eventHandler.raiseEvent("Volumes Changed", volumes); + } + + function recalculateUsingDailyFiles() { + + if (fileCursor.files.size === 0) { return; } // We need to wait until there are files in the cursor + + let daysOnSides = getSideDays(timePeriod); + + let leftDate = getDateFromPoint(viewPort.visibleArea.topLeft, thisObject.container, plotArea); + let rightDate = getDateFromPoint(viewPort.visibleArea.topRight, thisObject.container, plotArea); + + 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.getFullYear() + '-' + pad(currentDate.getMonth() + 1, 2) + '-' + pad(currentDate.getDate(), 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()) { + + volumes.push(volume); + + if (datetime.valueOf() >= volume.begin && datetime.valueOf() <= volume.end) { + + thisObject.container.eventHandler.raiseEvent("Current Candle 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); + + } + + function recalculateUsingMarketFiles() { + + if (marketFile === undefined) { return; } // Initialization not complete yet. + + let daysOnSides = getSideDays(timePeriod); + + let leftDate = getDateFromPoint(viewPort.visibleArea.topLeft, thisObject.container, plotArea); + let rightDate = getDateFromPoint(viewPort.visibleArea.topRight, thisObject.container, plotArea); + + 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()) { + + volumes.push(volume); + + if (datetime.valueOf() >= volume.begin && datetime.valueOf() <= volume.end) { + + thisObject.container.eventHandler.raiseEvent("Current Volume Changed", thisObject.currentCandle); + + } + } + } + + //console.log("Olivia > recalculateUsingMarketFiles > total volumes generated : " + volumes.length); + } + + function recalculateScaleX() { + + + var minValue = { + x: EARLIEST_DATE.valueOf() + }; + + var maxValue = { + x: MAX_PLOTABLE_DATE.valueOf() + }; + + plotArea.initializeX( + minValue, + maxValue, + thisObject.container.frame.width + ); + + plotAreaFrame.initializeX( + minValue, + maxValue, + thisObject.container.frame.width + ); + + } + + function recalculateScaleY() { + + var minValue = { + y: 0 + }; + + var maxValue = { + y: 0 + }; + + let timePeriodRatio = ONE_DAY_IN_MILISECONDS / timePeriod; + + maxValue.y = getMaxVolume() / (timePeriodRatio / 10); + + plotArea.initializeY( + minValue, + maxValue, + viewPort.visibleArea.bottomRight.y - viewPort.visibleArea.topLeft.y + ); + + plotAreaFrame.initializeY( + minValue, + maxValue, + thisObject.container.frame.height + ); + + function getMaxVolume() { + + let maxValue = 0; + + for (var i = 0; i < marketFile.length; i++) { + + let currentMax = (marketFile[i][0] + marketFile[i][1]) * 4; + + if (maxValue < currentMax) { + maxValue = currentMax; + } + } + + return maxValue; + + } + + } + + function plotChart() { + + if (volumes.length > 0) { + + /* Now we calculate and plot the volumes */ + + let visibleHeight = viewPort.visibleArea.bottomRight.y - 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 = transformThisPoint(frameCorner1, thisObject.container.frame.container); + frameCorner2 = transformThisPoint(frameCorner2, thisObject.container.frame.container); + + let frameHeightInViewPort = frameCorner2.y - frameCorner1.y; + + if (volumes.length > 0) { + + for (var 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 + timePeriod / 7 * 2, + y: 0 + }; + + volumePointA2 = { + x: volume.begin + timePeriod / 7 * 2, + y: volume.amountBuy + }; + + volumePointA3 = { + x: volume.begin + timePeriod / 7 * 5, + y: volume.amountBuy + }; + + volumePointA4 = { + x: volume.begin + timePeriod / 7 * 5, + y: 0 + }; + + volumePointA1 = plot.inverseTransform(volumePointA1, height); + volumePointA2 = plot.inverseTransform(volumePointA2, height); + volumePointA3 = plot.inverseTransform(volumePointA3, height); + volumePointA4 = plot.inverseTransform(volumePointA4, height); + + volumePointA1 = transformThisPoint(volumePointA1, thisObject.container); + volumePointA2 = transformThisPoint(volumePointA2, thisObject.container); + volumePointA3 = transformThisPoint(volumePointA3, thisObject.container); + volumePointA4 = 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 < viewPort.visibleArea.bottomLeft.x || volumePointA1.x > viewPort.visibleArea.bottomRight.x) { + return false; + } + + return true; + + } + + if (calculateBuys(plotAreaFrame, thisObject.container.frame.height) === false) { continue; } // We try to see if it fits in the visible area. + + if (volumePointA1.y > viewPort.visibleArea.bottomLeft.y && frameHeightInViewPort > visibleHeight * 2 / 3) { + + if (calculateBuys(plotArea, visibleHeight) === false) { continue; } // We snap t to the view port. + + /* Now we set the real value of y. */ + + volumePointA1.y = viewPort.visibleArea.bottomRight.y; + volumePointA2.y = viewPort.visibleArea.bottomRight.y - volume.amountBuy * plotArea.scale.y; + volumePointA3.y = viewPort.visibleArea.bottomRight.y - volume.amountBuy * plotArea.scale.y; + volumePointA4.y = viewPort.visibleArea.bottomRight.y; + + } + + let volumePointB1; + let volumePointB2; + let volumePointB3; + let volumePointB4; + + function calculateSells(plot, height) { + + volumePointB1 = { + x: volume.begin + timePeriod / 7 * 2, + y: height + }; + + volumePointB2 = { + x: volume.begin + timePeriod / 7 * 2, + y: height - volume.amountSell + }; + + volumePointB3 = { + x: volume.begin + timePeriod / 7 * 5, + y: height - volume.amountSell + }; + + volumePointB4 = { + x: volume.begin + timePeriod / 7 * 5, + y: height + }; + + volumePointB1 = plot.inverseTransform2(volumePointB1, height); + volumePointB2 = plot.inverseTransform2(volumePointB2, height); + volumePointB3 = plot.inverseTransform2(volumePointB3, height); + volumePointB4 = plot.inverseTransform2(volumePointB4, height); + + volumePointB1 = transformThisPoint(volumePointB1, thisObject.container); + volumePointB2 = transformThisPoint(volumePointB2, thisObject.container); + volumePointB3 = transformThisPoint(volumePointB3, thisObject.container); + volumePointB4 = transformThisPoint(volumePointB4, thisObject.container); + + } + + calculateSells(plotAreaFrame, thisObject.container.frame.height); // We try to see if it fits in the visible area. + + if (volumePointB1.y < viewPort.visibleArea.topLeft.y && frameHeightInViewPort > visibleHeight * 2 / 3) { + + calculateSells(plotArea, visibleHeight); // We snap it to the view port. + + /* Now we set the real value of y. */ + + volumePointB1.y = viewPort.visibleArea.topLeft.y; + volumePointB2.y = viewPort.visibleArea.topLeft.y + volume.amountSell * plotArea.scale.y; + volumePointB3.y = viewPort.visibleArea.topLeft.y + volume.amountSell * plotArea.scale.y; + volumePointB4.y = viewPort.visibleArea.topLeft.y; + + } + + /* 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 = viewPort.fitIntoVisibleArea(volumePointA1); + volumePointA2 = viewPort.fitIntoVisibleArea(volumePointA2); + volumePointA3 = viewPort.fitIntoVisibleArea(volumePointA3); + volumePointA4 = viewPort.fitIntoVisibleArea(volumePointA4); + + volumePointB1 = viewPort.fitIntoVisibleArea(volumePointB1); + volumePointB2 = viewPort.fitIntoVisibleArea(volumePointB2); + volumePointB3 = viewPort.fitIntoVisibleArea(volumePointB3); + volumePointB4 = viewPort.fitIntoVisibleArea(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 (datetime !== undefined) { + + let dateValue = datetime.valueOf(); + + if (dateValue >= volume.begin && dateValue <= volume.end) { + + browserCanvasContext.fillStyle = 'rgba(255, 233, 31, 0.40)'; // Current bar accroding to time + + } else { + + browserCanvasContext.fillStyle = 'rgba(64, 217, 26, 0.40)'; + } + + } else { + + browserCanvasContext.fillStyle = 'rgba(64, 217, 26, 0.40)'; + + } + + browserCanvasContext.fill(); + browserCanvasContext.strokeStyle = 'rgba(27, 105, 7, 0.40)'; + browserCanvasContext.lineWidth = 1; + 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 (datetime !== undefined) { + + let dateValue = datetime.valueOf(); + + if (dateValue >= volume.begin && dateValue <= volume.end) { + + browserCanvasContext.fillStyle = 'rgba(255, 233, 31, 0.40)'; // Current candle accroding to time + + } else { + + browserCanvasContext.fillStyle = 'rgba(219, 18, 18, 0.40)'; + } + + } else { + + browserCanvasContext.fillStyle = 'rgba(219, 18, 18, 0.40)'; + + } + + browserCanvasContext.strokeStyle = 'rgba(130, 9, 9, 0.40)'; + + browserCanvasContext.fill(); + browserCanvasContext.lineWidth = 1; + browserCanvasContext.stroke(); + + + + if (datetime !== undefined) { + + let dateValue = datetime.valueOf(); + + if (dateValue >= volume.begin && dateValue <= 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: timePeriod, + innerVolumeBar: volume + }; + + thisObject.container.eventHandler.raiseEvent("Current Volume Info Changed", currentVolume); + + } + } + } + } + } + } + + function onLayerStatusChanged(eventData) { + + if (eventData.layer === 'Olivia Volumes') { + layerStatus = eventData.status; + } + + } + + function onZoomChanged(event) { + + recalculateScaleX(); + recalculate(); + recalculateScaleY(); + + } + + function onDragFinished() { + + recalculateScaleX(); + recalculate(); + recalculateScaleY(); + + } +} + diff --git a/History.js b/History.js new file mode 100644 index 000000000..e2b70b21f --- /dev/null +++ b/History.js @@ -0,0 +1,694 @@ +function newAAMastersAAOliviaCandles() { + + let thisObject = { + + // Main functions and properties. + + initialize: initialize, + container: undefined, + getContainer: getContainer, + setTimePeriod: setTimePeriod, + setDatetime: setDatetime, + draw: draw, + + // Secondary functions and properties. + + currentCandle: undefined, + positionAtDatetime: positionAtDatetime, + onLayerStatusChanged: onLayerStatusChanged + }; + + /* 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 layerStatus = 'off'; // Since the moduleis a layer, it must handle the different possible layer status. + + let plotArea = newPlotArea(); // 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 marketFile; // This is the current Market File being plotted. + let fileCursor; // This is the current File Cursor being used to retrieve Daily Files. + + let fileCache; // This object will provide the different Market Files at different Time Periods. + let fileCursorCache; // This object will provide the different File Cursors at different Time Periods. + + /* 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. + + return thisObject; + + function initialize(pExchange, pMarket, pDatetime, pTimePeriod, pLayerStatus, callBackFunction) { + + let cursorCacheInProgress = false; + let finaleStepsInProgress = false; + + datetime = pDatetime; + timePeriod = pTimePeriod; + + fileCache = newFileCache(); + fileCache.initialize("AAMasters", "AAOlivia", "Market Candles", "Market Candlesticks", pExchange, pMarket, onFileReady); + + function onFileReady() { + + let newMarketFile = fileCache.getFile(ONE_DAY_IN_MILISECONDS); + + if (newMarketFile !== undefined) { + + marketFile = newMarketFile; + + initializeFileCursorCache(); + + } + } + + function initializeFileCursorCache() { + + if (cursorCacheInProgress === false) { + + cursorCacheInProgress = true; + + fileCursorCache = newFileCursorCache(); + fileCursorCache.initialize("AAMasters", "AAOlivia", "Daily Candles", "Daily Candlesticks", pExchange, pMarket, pDatetime, pTimePeriod, onFileCursorReady); + + } + } + + function onFileCursorReady() { + + recalculate(); + + let newFileCursor = fileCursorCache.getFileCursor(pTimePeriod); + + if (newFileCursor !== undefined) { // if the file ready is the one we need then it and we dont have it yet, then we will continue here. + + let stringDate = datetime.getUTCFullYear() + '-' + pad(datetime.getUTCMonth() + 1, 2) + '-' + pad(datetime.getUTCDate(), 2); + + let dailyFile = newFileCursor.files.get(stringDate); + + if (dailyFile !== undefined) { + + if (finaleStepsInProgress === false) { + + finaleStepsInProgress = true; + + fileCursor = newFileCursor; + + recalculateScale(); // With any of the market files we can calculate the scale. + + layerStatus = pLayerStatus; + + recalculate(); + + viewPort.eventHandler.listenToEvent("Zoom Changed", onZoomChanged); + canvas.eventHandler.listenToEvent("Drag Finished", onDragFinished); + + callBackFunction(); + + } + } + } + + } + + } + + function getContainer(point) { + + if (layerStatus !== 'on') { return; } + + 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 setTimePeriod(pTimePeriod) { + + timePeriod = pTimePeriod; + + if (timePeriod >= _1_HOUR_IN_MILISECONDS) { + + let newMarketFile = fileCache.getFile(pTimePeriod); + + if (newMarketFile !== undefined) { + + marketFile = newMarketFile; + + recalculate(); + + } + + } else { + + fileCursorCache.setTimePeriod(pTimePeriod); + + fileCursorCache.setDatetime(datetime); + + let newFileCursor = fileCursorCache.getFileCursor(pTimePeriod); + + if (newFileCursor !== undefined) { + + fileCursor = newFileCursor; + + recalculate(); + + } + } + + if (timePeriod === _1_HOUR_IN_MILISECONDS) { + + fileCursorCache.setTimePeriod(pTimePeriod); + + fileCursorCache.setDatetime(datetime); + + } + + } + + function setDatetime(newDatetime) { + + /* If there is a change in the day, then we take some actions, otherwise, we dont. */ + + let currentDate = Math.trunc(datetime.valueOf() / ONE_DAY_IN_MILISECONDS); + let newDate = Math.trunc(newDatetime.valueOf() / ONE_DAY_IN_MILISECONDS); + + datetime = newDatetime; + + if (currentDate !== newDate) { + + if (timePeriod <= _1_HOUR_IN_MILISECONDS) { + + fileCursorCache.setDatetime(newDatetime); + + } + } + } + + function positionAtDatetime(newDatetime) { + + value = newDatetime.valueOf(); + + /* Now we calculate which candle has this new time, because it will give us the y coordinate. */ + + for (let i = 0; i < candles.length; i++) { + + if (value >= candles[i].begin && value <= candles[i].end) { + + let targetPoint = { + x: value, + y: candles[i].open + }; + + targetPoint = plotArea.inverseTransform(targetPoint, thisObject.container.frame.height); + targetPoint = transformThisPoint(targetPoint, thisObject.container); + + let targetMax = { + x: value, + y: candles[i].max + }; + + targetMax = plotArea.inverseTransform(targetMax, thisObject.container.frame.height); + targetMax = transformThisPoint(targetMax, thisObject.container); + + let targetMin = { + x: value, + y: candles[i].min + }; + + targetMin = plotArea.inverseTransform(targetMin, thisObject.container.frame.height); + targetMin = transformThisPoint(targetMin, thisObject.container); + + let center = { + x: (viewPort.visibleArea.bottomRight.x - viewPort.visibleArea.bottomLeft.x) / 2, + y: (viewPort.visibleArea.bottomRight.y - viewPort.visibleArea.topRight.y) / 2 + }; + + if (targetMax.y < viewPort.visibleArea.topLeft.y || targetMin.y > viewPort.visibleArea.bottomRight.y) { + + let displaceVector = { + x: 0, + y: center.y - targetPoint.y + }; + + viewPort.displaceTarget(displaceVector); + + } + + let displaceVector = { + x: center.x - targetPoint.x, + y: 0 + }; + + viewPort.displace(displaceVector); + + return; + } + } + } + + function draw() { + + if (layerStatus !== 'on') { return; } + + this.container.frame.draw(); + + if (timePeriod < _1_HOUR_IN_MILISECONDS) { + + if (Math.random() * 1000 > 995) { + + recalculate(); + + } + } + + plotChart(); + + } + + function recalculate() { + + if (layerStatus === 'off') { return; } + + if (timePeriod >= _1_HOUR_IN_MILISECONDS) { + + recalculateUsingMarketFiles(); + + } else { + + recalculateUsingDailyFiles(); + + } + + thisObject.container.eventHandler.raiseEvent("Candles Changed", candles); + } + + function recalculateUsingDailyFiles() { + + if (fileCursor.files.size === 0) { return;} // We need to wait until there are files in the cursor + + let daysOnSides = getSideDays(timePeriod); + + let leftDate = getDateFromPoint(viewPort.visibleArea.topLeft, thisObject.container, plotArea); + let rightDate = getDateFromPoint(viewPort.visibleArea.topRight, thisObject.container, plotArea); + + 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.getFullYear() + '-' + pad(currentDate.getMonth() + 1, 2) + '-' + pad(currentDate.getDate(), 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'; } + + if (candle.begin >= farLeftDate.valueOf() && candle.end <= farRightDate.valueOf()) { + + candles.push(candle); + + if (datetime.valueOf() >= candle.begin && datetime.valueOf() <= candle.end) { + + thisObject.currentCandle = candle; + thisObject.container.eventHandler.raiseEvent("Current Candle 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); + + //console.log("File missing while calculating candles, scheduling a recalculation in 2 seconds."); + + } + } + + //console.log("Olivia > recalculateUsingDailyFiles > total candles generated : " + candles.length); + + } + + function recalculateUsingMarketFiles() { + + if (marketFile === undefined) { return; } // Initialization not complete yet. + + let daysOnSides = getSideDays(timePeriod); + + let leftDate = getDateFromPoint(viewPort.visibleArea.topLeft, thisObject.container, plotArea); + let rightDate = getDateFromPoint(viewPort.visibleArea.topRight, thisObject.container, plotArea); + + 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'; } + + if (candle.begin >= leftDate.valueOf() && candle.end <= rightDate.valueOf()) { + + candles.push(candle); + + if (datetime.valueOf() >= candle.begin && datetime.valueOf() <= candle.end) { + + thisObject.currentCandle = candle; + thisObject.container.eventHandler.raiseEvent("Current Candle Changed", thisObject.currentCandle); + + } + } + } + + //console.log("Olivia > recalculateUsingMarketFiles > total candles generated : " + candles.length); + } + + function recalculateScale() { + + if (marketFile === undefined) { return; } // We need the market file to be loaded to make the calculation. + + if (plotArea.maxValue > 0) { return; } // Already calculated. + + let minValue = { + x: EARLIEST_DATE.valueOf(), + y: 0 + }; + + let maxValue = { + x: MAX_PLOTABLE_DATE.valueOf(), + y: nextPorwerOf10(getMaxRate()) + }; + + + plotArea.initialize( + minValue, + maxValue, + thisObject.container.frame.width, + thisObject.container.frame.height + ); + + function getMaxRate() { + + let maxValue = 0; + + for (let i = 0; i < marketFile.length; i++) { + + let currentMax = marketFile[i][1]; // 1 = rates. + + if (maxValue < currentMax) { + maxValue = currentMax; + } + } + + return maxValue; + + } + + } + + function plotChart() { + + if (candles.length > 0) { + + /* Now we calculate and plot the candles */ + + for (let i = 0; i < candles.length; i++) { + + candle = candles[i]; + + let candlePoint1 = { + x: candle.begin + timePeriod / 7 * 1.5, + y: candle.open + }; + + let candlePoint2 = { + x: candle.begin + timePeriod / 7 * 5.5, + y: candle.open + }; + + let candlePoint3 = { + x: candle.begin + timePeriod / 7 * 5.5, + y: candle.close + }; + + let candlePoint4 = { + x: candle.begin + timePeriod / 7 * 1.5, + y: candle.close + }; + + candlePoint1 = plotArea.inverseTransform(candlePoint1, thisObject.container.frame.height); + candlePoint2 = plotArea.inverseTransform(candlePoint2, thisObject.container.frame.height); + candlePoint3 = plotArea.inverseTransform(candlePoint3, thisObject.container.frame.height); + candlePoint4 = plotArea.inverseTransform(candlePoint4, thisObject.container.frame.height); + + candlePoint1 = transformThisPoint(candlePoint1, thisObject.container); + candlePoint2 = transformThisPoint(candlePoint2, thisObject.container); + candlePoint3 = transformThisPoint(candlePoint3, thisObject.container); + candlePoint4 = transformThisPoint(candlePoint4, thisObject.container); + + if (candlePoint2.x < viewPort.visibleArea.bottomLeft.x || candlePoint1.x > viewPort.visibleArea.bottomRight.x) { + continue; + } + + candlePoint1 = viewPort.fitIntoVisibleArea(candlePoint1); + candlePoint2 = viewPort.fitIntoVisibleArea(candlePoint2); + candlePoint3 = viewPort.fitIntoVisibleArea(candlePoint3); + candlePoint4 = viewPort.fitIntoVisibleArea(candlePoint4); + + let stickPoint1 = { + x: candle.begin + timePeriod / 7 * 3.2, + y: candle.max + }; + + let stickPoint2 = { + x: candle.begin + timePeriod / 7 * 3.8, + y: candle.max + }; + + let stickPoint3 = { + x: candle.begin + timePeriod / 7 * 3.8, + y: candle.min + }; + + let stickPoint4 = { + x: candle.begin + timePeriod / 7 * 3.2, + y: candle.min + }; + + stickPoint1 = plotArea.inverseTransform(stickPoint1, thisObject.container.frame.height); + stickPoint2 = plotArea.inverseTransform(stickPoint2, thisObject.container.frame.height); + stickPoint3 = plotArea.inverseTransform(stickPoint3, thisObject.container.frame.height); + stickPoint4 = plotArea.inverseTransform(stickPoint4, thisObject.container.frame.height); + + stickPoint1 = transformThisPoint(stickPoint1, thisObject.container); + stickPoint2 = transformThisPoint(stickPoint2, thisObject.container); + stickPoint3 = transformThisPoint(stickPoint3, thisObject.container); + stickPoint4 = transformThisPoint(stickPoint4, thisObject.container); + + stickPoint1 = viewPort.fitIntoVisibleArea(stickPoint1); + stickPoint2 = viewPort.fitIntoVisibleArea(stickPoint2); + stickPoint3 = viewPort.fitIntoVisibleArea(stickPoint3); + stickPoint4 = viewPort.fitIntoVisibleArea(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(54, 54, 54, 1)'; + browserCanvasContext.fill(); + + if (datetime !== undefined) { + + let dateValue = datetime.valueOf(); + + if (dateValue >= candle.begin && dateValue <= candle.end) { + + browserCanvasContext.strokeStyle = 'rgba(255, 233, 31, 1)'; // Current candle accroding to time + + } else { + browserCanvasContext.strokeStyle = 'rgba(212, 206, 201, 1)'; + } + + } else { + browserCanvasContext.strokeStyle = 'rgba(212, 206, 201, 1)'; + } + + 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(); + + if (candle.direction === 'up') { browserCanvasContext.strokeStyle = 'rgba(27, 105, 7, 1)'; } + if (candle.direction === 'down') { browserCanvasContext.strokeStyle = 'rgba(130, 9, 9, 1)'; } + if (candle.direction === 'side') { browserCanvasContext.strokeStyle = 'rgba(27, 7, 105, 1)'; } + + if (datetime !== undefined) { + + let dateValue = datetime.valueOf(); + + if (dateValue >= candle.begin && dateValue <= candle.end) { + + /* highlight the current candle */ + + browserCanvasContext.fillStyle = 'rgba(255, 233, 31, 1)'; // Current candle accroding to time + + let currentCandle = { + bodyWidth: candlePoint2.x - candlePoint1.x, + bodyHeight: candlePoint3.y - candlePoint2.y, + stickHeight: stickPoint4.y - stickPoint2.y, + stickWidth: stickPoint2.x - stickPoint1.x, + stickStart: candlePoint2.y - stickPoint2.y, + period: timePeriod, + innerCandle: candle + }; + + thisObject.container.eventHandler.raiseEvent("Current Candle Info Changed", currentCandle); + + } else { + + if (candle.direction === 'up') { browserCanvasContext.fillStyle = 'rgba(64, 217, 26, 1)'; } + if (candle.direction === 'down') { browserCanvasContext.fillStyle = 'rgba(219, 18, 18, 1)'; } + if (candle.direction === 'side') { browserCanvasContext.fillStyle = 'rgba(64, 26, 217, 1)'; } + } + + } else { + + if (candle.direction === 'up') { browserCanvasContext.fillStyle = 'rgba(64, 217, 26, 1)'; } + if (candle.direction === 'down') { browserCanvasContext.fillStyle = 'rgba(219, 18, 18, 1)'; } + if (candle.direction === 'side') { browserCanvasContext.fillStyle = 'rgba(64, 26, 217, 1)'; } + + } + + + + if ( + candlePoint1.x < viewPort.visibleArea.topLeft.x + 50 + || + candlePoint1.x > viewPort.visibleArea.bottomRight.x - 50 + ) { + // we leave this candles without fill. + } else { + browserCanvasContext.fill(); + } + + browserCanvasContext.lineWidth = 1; + browserCanvasContext.stroke(); + + + } + } + } + + function onLayerStatusChanged(eventData) { + + if (eventData.layer === 'Olivia Candlesticks') { + layerStatus = eventData.status; + } + + } + + function onZoomChanged(event) { + + recalculate(); + + } + + function onDragFinished() { + + recalculate(); + + } +} + diff --git a/README.md b/README.md new file mode 100644 index 000000000..1fec2f25c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# AAMariam-Plotter diff --git a/bin/Microsoft.NodejsTools.WebRole.dll b/bin/Microsoft.NodejsTools.WebRole.dll new file mode 100644 index 000000000..03b93f4a1 Binary files /dev/null and b/bin/Microsoft.NodejsTools.WebRole.dll differ diff --git a/obj/Debug/AAOlivia-Plotter.njsproj.CoreCompileInputs.cache b/obj/Debug/AAOlivia-Plotter.njsproj.CoreCompileInputs.cache new file mode 100644 index 000000000..78474ee71 --- /dev/null +++ b/obj/Debug/AAOlivia-Plotter.njsproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +573924208ee1de43c28a684e3ad9af2dcef5974d diff --git a/obj/Debug/AAOlivia-Plotter.njsproj.FileListAbsolute.txt b/obj/Debug/AAOlivia-Plotter.njsproj.FileListAbsolute.txt new file mode 100644 index 000000000..4c3907976 --- /dev/null +++ b/obj/Debug/AAOlivia-Plotter.njsproj.FileListAbsolute.txt @@ -0,0 +1,3 @@ +C:\Users\Luis\source\repos\AAOlivia-Plotter\obj\Debug\AAOlivia-Plotter.njsprojResolveAssemblyReference.cache +C:\Users\Luis\source\repos\AAOlivia-Plotter\obj\Debug\AAOlivia-Plotter.njsproj.CoreCompileInputs.cache +C:\Users\Luis\source\repos\AAOlivia-Plotter\bin\Microsoft.NodejsTools.WebRole.dll diff --git a/obj/Debug/AAOlivia-Plotter.njsprojResolveAssemblyReference.cache b/obj/Debug/AAOlivia-Plotter.njsprojResolveAssemblyReference.cache new file mode 100644 index 000000000..67d5be9b7 Binary files /dev/null and b/obj/Debug/AAOlivia-Plotter.njsprojResolveAssemblyReference.cache differ