UNPKG

canvas-trading

Version:
225 lines (224 loc) 12.7 kB
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(); }