sclass TradingCandlePainter extends AbstractTickerPainter is IToolTipMaker { settable L candles; settable int spikeWidth = 1; settable Color shortColor = java.awt.Color.blue; settable Color longColor = java.awt.Color.yellow; // Heikin-Ashi style candles? settable bool heikinAshi; settable bool autoHorizontalRange = true; settable int maxCandlesToShow = 1000; *() {} *(L candles) { candles(candles); } TradingRun visiblePartOfRun() { int i = 0; while (i < l(candles) && candles.get(i).endOrProjectedEndTime() < horizontalRange().start) ++i; int j = l(candles); while (j > i && candles.get(j-1).startTime().unixDate() >= horizontalRange().end) --j; ret new TradingRun(subList(candles, i, j)); } void autoVerticalRange() { var run = visiblePartOfRun(); verticalRange(doubleRange(run.min(), run.max())); } public void drawOn(Graphics2D g) { if (empty(candles)) ret; var run = new TradingRun(candles); if (autoHorizontalRange() || horizontalRange() == null) { var firstCandle = candles.get(max(0, l(candles)-maxCandlesToShow)); horizontalRange(doubleRange(firstCandle.startTime().unixDate(), run.endOrProjectedEndTime())); } autoVerticalRange(); drawPercentLines(g); drawAdditionalObjects(g); var candles = candles(); if (heikinAshi) candles = convertToHeikinAshi(candles); // Draw candles var nCandles = l(candles); for i to nCandles: { var candle = candles.get(i); //var x1 = xRange().start()+doubleRatio(i, nCandles)*xRange().length(); //var x2 = xRange().start()+doubleRatio(i+1, nCandles)*xRange().length(); double x1 = xToScreen(candle.startTime().unixDate()); double x2 = xToScreen(candle.endOrProjectedEndTime()); x2 = blend(x1, x2, 0.75); var xCenter = avg(x1, x2); var y1 = yToScreen(candle.max()); var y2 = yToScreen(candle.min()); var yStart = yToScreen(candle.start()); var yEnd = yToScreen(candle.end()); // draw "spikes" var r = doubleRectFromPoints(xCenter, y1, xCenter+spikeWidth, y2); fillRect(g, growRectBottom(1, toRect_round(r)), candle.color()); // draw candle body r = doubleRectFromPoints(x1, min(yStart, yEnd), x2, max(yStart, yEnd)); fillRect(g, growRectBottom(1, toRect_round(r)), candle.color()); } drawPositions(g); } public BufferedImage render() { var img = super.render(); var img2 = cloneBufferedImageWithMeta(img); metaSet(img2, IToolTipMaker, this); ret img2; } public swappable S getToolTip(Pt p) { if (!hasScale()) ret "No candles yet"; double time = xFromScreen(p.x); //double price = yFromScreen(p.y); TradingCandle candle = firstThat(candles, c -> c.startTime().unixDate() >= time); if (candle == null) ret formatLocalDateWithSeconds(lround(time)); ret str(candle); } selfType candles(TradingCandleMaker candleMaker) { ret candles(candleMaker!); } }