canvas-trading
Version:
Interactive HTML5 Canvas Trading Chart
225 lines (224 loc) • 12.7 kB
JavaScript
import { alligatorLinesSettings, candleColors, canvasSettings, tradeColors } from '../config';
import { roundedRect, line, rect, drawMountedIndicators, drawCurveLine, arrowWithHead, fibonacciNumbers, fibonacciReference, } from './drawFunctions';
import { Candle2D } from '../classes/CandleClasses';
export function displayTrade(ctx, candleCanvas, tradeCandles) {
var _a, _b, _c;
var startCandle = tradeCandles.startCandle, endCandle = tradeCandles.endCandle;
// draw line from buy to sell
if (startCandle.candle &&
endCandle.candle &&
candleCanvas.candleArray.length // candleArray is an empty array ([]) for a quick moment right after the first render when: User switches to a shorter (new) candle history and the current scroll and zoom make it so that the user's view zone catches any candles from `the short (old) candle history length` to `candlesShown (zoom) + the short (old) candle history length`. In other words, for a test, you can comment out this length check, go to the 2nd example in the demo, scroll to the maximum left (don't zoom, it's not relevant for this test), switch to the 1st example, scroll ~1-10 candles further left, and switch back to the 2nd example. If the page crashes — then this length check is still relevant. P.S. the page crash reason will be the `candleCanvas.candleArray[0]` evaluating to `undefined`, thus making `.originalIndex` access critically invalid. The `candlesShown (zoom)` on demo for the moment of this test was 160.
) {
var originalIndexOfFirstVisibleCandle = candleCanvas.candleArray[0].originalIndex;
var startCandleIndex = startCandle.index - originalIndexOfFirstVisibleCandle + candleCanvas.candleShift; // will be negative if the candle is not visible
var originalEndCandleIndex = endCandle.index - originalIndexOfFirstVisibleCandle + candleCanvas.candleShift;
/** if the trade is all the way (fully) hidden outside the viewport borders. */
var isNotInViewport = (originalEndCandleIndex < 0 && startCandleIndex < 0) ||
(originalEndCandleIndex >= candleCanvas.candlesShown &&
startCandleIndex >= candleCanvas.candlesShown);
if (isNotInViewport)
return;
var buyPrice = (_a = startCandle.candle.trades) === null || _a === void 0 ? void 0 : _a[startCandle.innerIndex].buyPrice;
var sellPrice = (_b = endCandle.candle.trades) === null || _b === void 0 ? void 0 : _b[endCandle.innerIndex].sellPrice;
var isTradeLong = ((_c = endCandle.candle.trades) === null || _c === void 0 ? void 0 : _c[endCandle.innerIndex].tradeType) === 'long';
var isProfit = isTradeLong ? buyPrice < sellPrice : buyPrice > sellPrice;
var start = {
x: startCandleIndex * (candleCanvas.candleWidth + candleCanvas.gap) +
candleCanvas.candleWidth / 2,
y: Candle2D.getPoint(buyPrice, candleCanvas),
};
var end = {
x: originalEndCandleIndex * (candleCanvas.candleWidth + candleCanvas.gap) +
candleCanvas.candleWidth / 2,
y: Candle2D.getPoint(sellPrice, candleCanvas),
};
var tradeInOneCandle = startCandle.candle.openTime === endCandle.candle.openTime;
if (!tradeInOneCandle) {
// draw filled rect behind the line
roundedRect(ctx, start.x, Math.min(start.y, end.y), end.x - start.x, Math.abs(end.y - start.y), candleCanvas.width / 150, isProfit ? tradeColors.positiveRect : tradeColors.negativeRect);
arrowWithHead(ctx, start, end, tradeColors.arrow, candleCanvas.candleWidth);
}
}
return { startCandle: startCandle, endCandle: endCandle };
}
export function drawCursor(ctx, canvasWidth, canvasHeight, cursor) {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
line(ctx, { x: cursor.x * canvasSettings.scaleForQuality, y: 0 }, { x: cursor.x * canvasSettings.scaleForQuality, y: canvasHeight }, 'white', 0.7, 0.85, [20 - canvasHeight / 100, 20 - canvasHeight / 100]);
line(ctx, { x: 0, y: cursor.y * canvasSettings.scaleForQuality }, { x: canvasWidth, y: cursor.y * canvasSettings.scaleForQuality }, 'white', 0.7, 0.85, [20 - canvasHeight / 100, 20 - canvasHeight / 100]);
}
export function drawFibonacciRetracements(ctx, candleCanvas, fiboStartPrice, fiboEndPrice, fiboStartIndex, fiboEndIndex) {
// if (candleCanvas.candleArray.length === 0) return;
// console.log('test', candleCanvas.candleArray.length);
var originalIndexOfFirstVisibleCandle = candleCanvas.candleArray[0].originalIndex;
var fiboStartGoodIndex = fiboStartIndex - originalIndexOfFirstVisibleCandle + candleCanvas.candleShift;
var fiboEndGoodIndex = fiboEndIndex - originalIndexOfFirstVisibleCandle + candleCanvas.candleShift;
// console.log('fiboStartGoodIndex', fiboStartGoodIndex, 'fiboEndGoodIndex', fiboEndGoodIndex);
var fiboStart = {
x: fiboStartGoodIndex * (candleCanvas.candleWidth + candleCanvas.gap) +
candleCanvas.candleWidth / 2,
y: Candle2D.getPoint(fiboStartPrice, candleCanvas),
};
var fiboEnd = {
x: fiboEndGoodIndex * (candleCanvas.candleWidth + candleCanvas.gap) +
candleCanvas.candleWidth / 2,
y: Candle2D.getPoint(fiboEndPrice, candleCanvas),
};
var fiboWidth = Math.abs(fiboEnd.x - fiboStart.x);
var levels = fibonacciNumbers(fiboEndPrice, fiboStartPrice);
levels.forEach(function (level, i) {
var fibonacciColors = [
// classical
// '#797B86',
// '#F23645',
// '#FF9800',
// '#4CAF50',
// '#049981',
// '#00BCD4',
// '#797B86',
// custom
'#B2B5BE',
'#B2B5BE',
'#B2B5BE',
'#FFEB3C',
'#9D27B3',
'#B2B5BE',
'#B2B5BE',
];
var y = Candle2D.getPoint(level, candleCanvas);
line(ctx, { x: fiboStart.x, y: y }, { x: fiboStart.x + fiboWidth, y: y }, fibonacciColors[i], 2);
// draw the % value on the laft of the line
var text = String(fibonacciReference[i]);
ctx.beginPath();
ctx.fillStyle = i === 6 ? '#ddd' : fibonacciColors[i];
var fontWeight = 250;
var fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif";
ctx.font = "".concat(fontWeight, " ").concat(candleCanvas.height / 30, "px ").concat(fontFamily);
var metrics = ctx.measureText(text);
var x = fiboStart.x - metrics.width - 20;
var centerYOffset = metrics.actualBoundingBoxAscent / 2 - metrics.actualBoundingBoxDescent;
var yText = y + centerYOffset;
ctx.shadowColor = 'black';
ctx.shadowBlur = 3;
ctx.fillText(text, x, yText);
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.closePath();
//draw a rectangle with opacity from current line to the next
if (i === 0)
return;
// if (fiboStartPrice < fiboEndPrice) {
var prevY = Candle2D.getPoint(levels[i - 1], candleCanvas);
var rectHeight = Math.abs(y - prevY);
rect(ctx, fiboStart.x,
//? The initial Y depends on the direction of the fibonacci retracement.
fiboStartPrice < fiboEndPrice ? prevY : y, fiboWidth, rectHeight, fibonacciColors[i], 0.25);
});
// draw fibo line
line(ctx, fiboStart, fiboEnd, 'white', 1, 0.8, [
20 - candleCanvas.height / 100,
20 - candleCanvas.height / 100,
]);
//? draw 2 small circles on the start and end of the fibo line
[fiboStart, fiboEnd].forEach(function (circle) {
ctx.beginPath();
ctx.strokeStyle = '#54A3FF';
ctx.lineWidth = 3;
ctx.arc(circle.x, circle.y, 15 - candleCanvas.height / 100, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
});
}
export function drawSimpleLine(ctx, candleCanvas, startPrice, endPrice, startIndex, endIndex, color, opacity, dash) {
if (opacity === void 0) { opacity = 0.8; }
if (dash === void 0) { dash = [20, 20]; }
var originalIndexOfFirstVisibleCandle = candleCanvas.candleArray[0].originalIndex;
var startGoodIndex = startIndex - originalIndexOfFirstVisibleCandle + candleCanvas.candleShift;
var endGoodIndex = endIndex - originalIndexOfFirstVisibleCandle + candleCanvas.candleShift;
var start = {
x: startGoodIndex * (candleCanvas.candleWidth + candleCanvas.gap) + candleCanvas.candleWidth / 2,
y: Candle2D.getPoint(startPrice, candleCanvas),
};
var end = {
x: endGoodIndex * (candleCanvas.candleWidth + candleCanvas.gap) + candleCanvas.candleWidth / 2,
y: Candle2D.getPoint(endPrice, candleCanvas),
};
line(ctx, start, end, color, 2, opacity, [
dash[0] - candleCanvas.height / 100,
dash[1] - candleCanvas.height / 100,
]);
}
export function drawAo(ctx, candleCanvas) {
//clear canvas
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
candleCanvas.aoArray.forEach(function (ao, i) {
var _a;
rect(ctx, ao.x, ao.y, candleCanvas.candleWidth, ao.height, ao.vertexValue > ((_a = candleCanvas.aoArray[i - 1]) === null || _a === void 0 ? void 0 : _a.vertexValue)
? candleColors.green
: candleColors.red);
});
}
export function drawStdev(ctx,
// candleCanvas: CandleCanvas
stdevArray) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawCurveLine(ctx, stdevArray, '#009EE2', 2, 'round');
}
export var drawFunction = function (ctx, canvas, otherSettings) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// alligator should be probably drawn first as it is at the back
if (otherSettings.alligator) {
var alligatorWidth_1 = Math.sqrt(canvas.candleWidth * alligatorLinesSettings.lineWeight);
var alligatorKeys = Object.keys(canvas.alligatorArray);
alligatorKeys.forEach(function (key) {
drawCurveLine(ctx, canvas.alligatorArray[key], alligatorLinesSettings[key], alligatorWidth_1, 'round');
});
}
canvas.candleArray.forEach(function (candle) {
if (candle.noDraw)
return;
var x = candle.xPosition;
var candleIsRed = candle.open < candle.close;
var y = candleIsRed ? candle.open : candle.close;
/** Should be white when it closed at the same price it opened. */
var candleIsWhite = candle.open === candle.close;
var fixedWidthIfCandleIsWhite = 3;
var candleFillColor = candleIsWhite
? 'white'
: candleIsRed
? candleColors.red
: candleColors.green;
// draw candle
rect(ctx, x, y, canvas.candleWidth, candleIsWhite ? fixedWidthIfCandleIsWhite : Math.abs(candle.open - candle.close), candleFillColor);
// draw wick
var wickX = x + canvas.candleWidth / 2;
line(ctx, { x: wickX, y: candle.high }, { x: wickX, y: candle.low }, candleFillColor, canvas.candleWidth / 6);
// draw indicators
if (otherSettings.mountedIndicators)
drawMountedIndicators(ctx, candle, x, canvas.candleWidth, otherSettings.drawRevBar, otherSettings.drawFractal);
});
drawLastCandlePrice(ctx, canvas, otherSettings);
};
function drawLastCandlePrice(ctx, canvas, otherSettings) {
if (!otherSettings.showLastCandlePrice)
return;
var lastCandle = canvas.lastCandle;
if (!lastCandle || !lastCandle.close)
return; // lastCandle.close may be undefined on DF3 sometimes. Idk why.
var alligatorOffset = 8;
var lastCandle2D = canvas.candleArray[canvas.candleArray.length - (1 + alligatorOffset) + canvas.candleShift];
if (!lastCandle2D)
return;
ctx.beginPath();
ctx.fillStyle = 'gray';
var fontWeight = 200;
var fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif";
ctx.font = "".concat(fontWeight, " ").concat(canvas.height / 25, "px ").concat(fontFamily);
var text = "-- ".concat(Number(lastCandle.close.toFixed(2)));
var metrics = ctx.measureText(text);
var x = lastCandle2D.xPosition + canvas.candleWidth * 2 + canvas.gap;
/** So that the '--' in the start of the text exactly matches the position of the candle body bottom. */
var centerYOffset = metrics.actualBoundingBoxAscent / 2 - metrics.actualBoundingBoxDescent;
var y = lastCandle2D.close + centerYOffset;
ctx.fillText(text, x, y);
ctx.closePath();
}