Files

727 lines
27 KiB
JavaScript

function newBollingerPlottersBollingerBandsBollingerBands() {
const MODULE_NAME = "Bands Plotter";
const ERROR_LOG = true;
const INTENSIVE_LOG = false;
const logger = newWebDebugLog();
let thisObject = {
// Main functions and properties.
initialize: initialize,
finalize: finalize,
container: undefined,
fitFunction: undefined,
getContainer: getContainer,
setTimeFrame: setTimeFrame,
setDatetime: setDatetime,
setCoordinateSystem: setCoordinateSystem,
draw: draw,
/* Events declared outside the plotter. */
onDailyFileLoaded: onDailyFileLoaded,
// Secondary functions and properties.
currentBand: undefined
};
/* this is part of the module template */
let container = newContainer(); // Do not touch this 3 lines, they are just needed.
container.initialize();
thisObject.container = container;
let 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 bands = []; // Here we keep the bands to be plotted 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 necessary events. */
thisObject.container.eventHandler.stopListening(onMouseOverEventSuscriptionId)
UI.projects.foundations.spaces.chartingSpace.viewport.eventHandler.stopListening(zoomChangedEventSubscriptionId);
UI.projects.foundations.spaces.chartingSpace.viewport.eventHandler.stopListening(offsetChangedEventSubscriptionId);
canvas.eventHandler.stopListening(dragFinishedEventSubscriptionId);
thisObject.container.eventHandler.stopListening(dimmensionsChangedEventSubscriptionId)
marketFiles.eventHandler.stopListening(marketFilesUpdatedEventSubscriptionId);
dailyFiles.eventHandler.stopListening(dailyFilesUpdatedEventSubscriptionId);
/* Destroyed 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 Time Frame. */
marketFile = marketFiles.getFile(pTimeFrame);
fileCursor = dailyFiles.getFileCursor(pTimeFrame);
/* Listen to the necessary events. */
zoomChangedEventSubscriptionId = UI.projects.foundations.spaces.chartingSpace.viewport.eventHandler.listenToEvent("Zoom Changed", onViewportZoomChanged);
offsetChangedEventSubscriptionId = UI.projects.foundations.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.foundations.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 (this.container.frame.isThisPointHere(point) === true) {
return this.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 {
this.container.frame.draw();
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("Bands Changed", bands);
} 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.foundations.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem);
let rightDate = UI.projects.foundations.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.foundations.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());
bands = [];
let lastBand
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 band = {
begin: undefined,
end: undefined,
movingAverage: undefined,
standardDeviation: undefined,
deviation: undefined
};
band.begin = dailyFile[i][0];
band.end = dailyFile[i][1];
band.movingAverage = dailyFile[i][2];
band.standardDeviation = dailyFile[i][3];
band.deviation = dailyFile[i][4];
if (lastBand !== undefined) {
if (lastBand.movingAverage > band.movingAverage) { band.direction = "Down" }
if (lastBand.movingAverage < band.movingAverage) { band.direction = "Up" }
if (lastBand.movingAverage === band.movingAverage) { band.direction = "Side" }
}
lastBand = band
if (
(band.begin >= farLeftDate.valueOf() && band.end <= farRightDate.valueOf()) &&
(band.begin >= coordinateSystem.min.x && band.end <= coordinateSystem.max.x)
) {
bands.push(band);
if (datetime.valueOf() >= band.begin && datetime.valueOf() <= band.end) {
thisObject.currentBand = band;
thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentBand);
}
}
}
}
currentDate = new Date(currentDate.valueOf() + ONE_DAY_IN_MILISECONDS);
}
/* Lets check if all the visible screen is going to be covered by bands. */
let lowerEnd = leftDate.valueOf();
let upperEnd = rightDate.valueOf();
if (bands.length > 0) {
if (bands[0].begin > lowerEnd || bands[bands.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.foundations.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem);
let rightDate = UI.projects.foundations.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.foundations.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);
bands = [];
let lastBand
for (let i = 0; i < marketFile.length; i++) {
let band = {
begin: undefined,
end: undefined,
movingAverage: undefined,
standardDeviation: undefined,
deviation: undefined
};
band.begin = marketFile[i][0];
band.end = marketFile[i][1];
band.movingAverage = marketFile[i][2];
band.standardDeviation = marketFile[i][3];
band.deviation = marketFile[i][4];
if (lastBand !== undefined) {
if (lastBand.movingAverage > band.movingAverage) { band.direction = "Down" }
if (lastBand.movingAverage < band.movingAverage) { band.direction = "Up" }
if (lastBand.movingAverage === band.movingAverage) { band.direction = "Side" }
}
lastBand = band
if (
(band.begin >= leftDate.valueOf() && band.end <= rightDate.valueOf()) &&
(band.begin >= coordinateSystem.min.x && band.end <= coordinateSystem.max.x)
) {
bands.push(band);
if (datetime.valueOf() >= band.begin && datetime.valueOf() <= band.end) {
thisObject.currentBand = band;
thisObject.container.eventHandler.raiseEvent("Current Record Changed", thisObject.currentBand);
}
}
}
} catch (err) {
if (ERROR_LOG === true) { logger.write("[ERROR] recalculateUsingMarketFiles -> err = " + err.stack); }
}
}
function plotChart() {
try {
let band;
let previousBand;
if (bands.length > 0) {
/* Now we calculate and plot the bands */
for (let i = 1; i < bands.length; i++) {
band = bands[i];
previousBand = bands[i - 1];
/* From here we draw the actual Bollinger Bands and lines it contains. */
let bandPoint1 = {
x: band.begin,
y: previousBand.movingAverage - previousBand.deviation
};
let bandPoint2 = {
x: band.begin,
y: previousBand.movingAverage + previousBand.deviation
};
let bandPoint3 = {
x: band.end,
y: band.movingAverage + band.deviation
};
let bandPoint4 = {
x: band.end,
y: band.movingAverage - band.deviation
};
let bandPoint5 = {
x: band.begin,
y: previousBand.movingAverage
};
let bandPoint6 = {
x: band.end,
y: band.movingAverage
};
bandPoint1 = coordinateSystem.transformThisPoint(bandPoint1);
bandPoint2 = coordinateSystem.transformThisPoint(bandPoint2);
bandPoint3 = coordinateSystem.transformThisPoint(bandPoint3);
bandPoint4 = coordinateSystem.transformThisPoint(bandPoint4);
bandPoint5 = coordinateSystem.transformThisPoint(bandPoint5);
bandPoint6 = coordinateSystem.transformThisPoint(bandPoint6);
bandPoint1 = UI.projects.foundations.utilities.coordinateTransformations.transformThisPoint(bandPoint1, thisObject.container);
bandPoint2 = UI.projects.foundations.utilities.coordinateTransformations.transformThisPoint(bandPoint2, thisObject.container);
bandPoint3 = UI.projects.foundations.utilities.coordinateTransformations.transformThisPoint(bandPoint3, thisObject.container);
bandPoint4 = UI.projects.foundations.utilities.coordinateTransformations.transformThisPoint(bandPoint4, thisObject.container);
bandPoint5 = UI.projects.foundations.utilities.coordinateTransformations.transformThisPoint(bandPoint5, thisObject.container);
bandPoint6 = UI.projects.foundations.utilities.coordinateTransformations.transformThisPoint(bandPoint6, thisObject.container);
if (bandPoint2.x < UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.bottomLeft.x || bandPoint1.x > UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.bottomRight.x) {
continue;
}
if (UI.projects.foundations.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.foundations.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_PACKING_OBJECTS_AT_THE_BOTTOM_OR_TOP_OF_VIEWPORT) {
let diffA = bandPoint1.y - UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y
if (diffA > 0) {
bandPoint1.y = bandPoint1.y - diffA
bandPoint2.y = bandPoint2.y - diffA
bandPoint3.y = bandPoint3.y - diffA
bandPoint4.y = bandPoint4.y - diffA
bandPoint5.y = bandPoint5.y - diffA
bandPoint6.y = bandPoint6.y - diffA
}
let diffB = bandPoint2.y - UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.topLeft.y
if (diffB < 0) {
bandPoint1.y = bandPoint1.y - diffB
bandPoint2.y = bandPoint2.y - diffB
bandPoint3.y = bandPoint3.y - diffB
bandPoint4.y = bandPoint4.y - diffB
bandPoint5.y = bandPoint5.y - diffB
bandPoint6.y = bandPoint6.y - diffB
}
}
bandPoint1 = UI.projects.foundations.spaces.chartingSpace.viewport.fitIntoVisibleArea(bandPoint1);
bandPoint2 = UI.projects.foundations.spaces.chartingSpace.viewport.fitIntoVisibleArea(bandPoint2);
bandPoint3 = UI.projects.foundations.spaces.chartingSpace.viewport.fitIntoVisibleArea(bandPoint3);
bandPoint4 = UI.projects.foundations.spaces.chartingSpace.viewport.fitIntoVisibleArea(bandPoint4);
bandPoint5 = UI.projects.foundations.spaces.chartingSpace.viewport.fitIntoVisibleArea(bandPoint5);
bandPoint6 = UI.projects.foundations.spaces.chartingSpace.viewport.fitIntoVisibleArea(bandPoint6);
bandPoint1 = thisObject.fitFunction(bandPoint1);
bandPoint2 = thisObject.fitFunction(bandPoint2);
bandPoint3 = thisObject.fitFunction(bandPoint3);
bandPoint4 = thisObject.fitFunction(bandPoint4);
bandPoint5 = thisObject.fitFunction(bandPoint5);
bandPoint6 = thisObject.fitFunction(bandPoint6);
/* First we are drawing a semi-transparent background */
browserCanvasContext.beginPath();
browserCanvasContext.moveTo(bandPoint1.x, bandPoint1.y);
browserCanvasContext.lineTo(bandPoint2.x, bandPoint2.y);
browserCanvasContext.lineTo(bandPoint3.x, bandPoint3.y);
browserCanvasContext.lineTo(bandPoint4.x, bandPoint4.y);
browserCanvasContext.closePath();
browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', 0.05)';
if (userPositionDate >= band.begin && userPositionDate <= band.end) {
/* highlight the current band */
browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 0.05)'; // Current band according to time
}
if (
bandPoint1.x < UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.topLeft.x + 150
||
bandPoint1.x > UI.projects.foundations.spaces.chartingSpace.viewport.visibleArea.bottomRight.x - 150
) {
// we leave this bands without fill.
} else {
browserCanvasContext.fill();
}
/* Next we are drawing the outer bands lines */
browserCanvasContext.beginPath();
browserCanvasContext.moveTo(bandPoint1.x, bandPoint1.y);
browserCanvasContext.lineTo(bandPoint4.x, bandPoint4.y);
browserCanvasContext.moveTo(bandPoint2.x, bandPoint2.y);
browserCanvasContext.lineTo(bandPoint3.x, bandPoint3.y);
browserCanvasContext.closePath();
browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', 1)';
if (userPositionDate >= band.begin && userPositionDate <= band.end) {
/* highlight the current band */
browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 1)'; // Current band according to time
let currentBand = {
bodyWidth: bandPoint3.x - bandPoint2.x,
leftBodyHeight: bandPoint2.y - bandPoint1.y,
rightBodyHeight: bandPoint3.y - bandPoint4.y,
topDelta: bandPoint3.y - bandPoint2.y,
bottomDelta: bandPoint4.y - bandPoint1.y,
timeFrame: timeFrame,
innerBand: band
};
thisObject.container.eventHandler.raiseEvent("Current Record Changed", currentBand);
}
browserCanvasContext.lineWidth = 0.2;
browserCanvasContext.setLineDash([]) // Resets Line Dash
browserCanvasContext.stroke();
/* Finally we draw the moving average line */
browserCanvasContext.beginPath();
browserCanvasContext.moveTo(bandPoint5.x, bandPoint5.y);
browserCanvasContext.lineTo(bandPoint6.x, bandPoint6.y);
browserCanvasContext.closePath();
if (bandPoint5.y > bandPoint6.y) {
browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', 1)';
} else {
browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', 1)';
}
if (userPositionDate >= band.begin && userPositionDate <= band.end) {
/* highlight the current band */
browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 1)'; // Current band according to time
}
browserCanvasContext.lineWidth = 0.2;
browserCanvasContext.setLineDash([]) // Resets Line Dash
browserCanvasContext.stroke();
/* Contributing to Auto-Scale*/
coordinateSystem.reportYValue(band.movingAverage + band.deviation)
coordinateSystem.reportYValue(band.movingAverage - band.deviation)
}
}
} 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); }
}
}
}