All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.gsi.chart.renderer.spi.financial.FootprintRenderer Maven / Gradle / Ivy

package de.gsi.chart.renderer.spi.financial;

import static com.sun.javafx.scene.control.skin.Utils.computeTextWidth;

import static de.gsi.chart.renderer.spi.financial.css.FinancialCss.*;
import static de.gsi.chart.renderer.spi.financial.service.footprint.FootprintRendererAttributes.BID_ASK_VOLUME_FONTS;
import static de.gsi.dataset.DataSet.DIM_X;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javafx.collections.ObservableList;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;

import com.sun.javafx.tk.FontLoader;
import com.sun.javafx.tk.FontMetrics;
import com.sun.javafx.tk.Toolkit;

import de.gsi.chart.Chart;
import de.gsi.chart.XYChart;
import de.gsi.chart.axes.Axis;
import de.gsi.chart.axes.spi.CategoryAxis;
import de.gsi.chart.renderer.Renderer;
import de.gsi.chart.renderer.spi.financial.service.OhlcvRendererEpData;
import de.gsi.chart.renderer.spi.financial.service.RendererPaintAfterEP;
import de.gsi.chart.renderer.spi.financial.service.RendererPaintAfterEPAware;
import de.gsi.chart.renderer.spi.financial.service.footprint.FootprintRendererAttributes;
import de.gsi.chart.renderer.spi.financial.service.footprint.NbColumnColorGroup;
import de.gsi.chart.renderer.spi.financial.service.footprint.NbColumnColorGroup.FontColor;
import de.gsi.chart.renderer.spi.utils.DefaultRenderColorScheme;
import de.gsi.chart.utils.StyleParser;
import de.gsi.dataset.DataSet;
import de.gsi.dataset.spi.financial.api.attrs.AttributeModelAware;
import de.gsi.dataset.spi.financial.api.ohlcv.IOhlcvItem;
import de.gsi.dataset.spi.financial.api.ohlcv.IOhlcvItemAware;
import de.gsi.dataset.utils.ProcessingProfiler;

/**
 * Footprint Chart Renderer
 *

* Footprint chart is a type of candlestick chart that provides additional information, such as trade volume and order flow, * in addition to price. It is multi-dimensional in nature, and can provide an investor with more information for analysis, * beyond just the security's price. This tool is a unique offering that is gaining popularity amongst leading charting software providers. *

* Footprint charts provide the benefit of analyzing multiple variables in a focused diagram. * Common footprint charts include footprint profile, bid/ask footprint, delta footprint, and volume footprint. *

* Bid/Ask Footprint: Adds color to the real-time volume, for easier visualization of buyers and sellers probing the bid or ask. * With this footprint, traders can see whether the buyers or the sellers are the responsible parties, for influencing a price move. *

* @see Footprint Charts Investopedia * * @author afischer */ @SuppressWarnings({ "PMD.ExcessiveMethodLength", "PMD.NPathComplexity", "PMD.ExcessiveParameterList" }) // designated purpose of this class public class FootprintRenderer extends AbstractFinancialRenderer implements Renderer, RendererPaintAfterEPAware { private final static double FONT_RATIO = 13.0; private final boolean paintVolume; private final boolean paintPoc; private final boolean paintPullbackColumn; private final FindAreaDistances findAreaDistances; private final IFootprintRenderedAPI footprintRenderedApi; private final FootprintRendererAttributes footprintAttrs; private final FontLoader fontLoader; private AttributeModelAware attrs; private IOhlcvItemAware itemAware; private boolean isEpAvailable; private Color pocColor; private Color footprintDefaultFontColor; private Color footprintCrossLineColor; private Color footprintBoxLongColor; private Color fooprintBoxShortColor; private Color footprintVolumeLongColor; private Color footprintVolumeShortColor; private double[] distances; private int iMin; private int iMax; private double localBarWidth; private double barWidthHalf; private double ratio; private Font basicFont; private Font selectedFont; private double fontGap; private double basicGap; private float heightText; protected List paintAfterEPS = new ArrayList<>(); public FootprintRenderer(IFootprintRenderedAPI footprintRenderedApi, boolean paintVolume, boolean paintPoc, boolean paintPullbackColumn) { this.footprintRenderedApi = footprintRenderedApi; this.footprintAttrs = footprintRenderedApi.getFootprintAttributes(); this.paintVolume = paintVolume; this.paintPoc = paintPoc; this.paintPullbackColumn = paintPullbackColumn; this.findAreaDistances = paintVolume ? new XMinVolumeMaxAreaDistances() : new XMinAreaDistances(); fontLoader = Toolkit.getToolkit().getFontLoader(); } public FootprintRenderer(IFootprintRenderedAPI footprintRenderedApi) { this(footprintRenderedApi, false, true, true); } public boolean isPaintVolume() { return paintVolume; } public boolean isPaintPoc() { return paintPoc; } public boolean isPaintPullbackColumn() { return paintPullbackColumn; } @Override public Canvas drawLegendSymbol(DataSet dataSet, int dsIndex, int width, int height) { final Canvas canvas = new Canvas(width, height); final GraphicsContext gc = canvas.getGraphicsContext2D(); final String style = dataSet.getStyle(); gc.save(); Color candleLongColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_LONG_COLOR, Color.GREEN); Color candleShortColor = StyleParser.getColorPropertyValue(style, DATASET_CANDLESTICK_SHORT_COLOR, Color.RED); gc.setFill(candleLongColor); gc.setStroke(candleLongColor); gc.fillRect(1, 3, width / 2.0 - 2.0, height - 8.0); double x = width / 4.0; gc.strokeLine(x, 1, x, height - 2.0); gc.setFill(candleShortColor); gc.setStroke(candleShortColor); gc.fillRect(width / 2.0 + 2.0, 4, width - 2.0, height - 12.0); x = 3.0 * width / 4.0 + 1.5; gc.strokeLine(x, 1, x, height - 3.0); gc.restore(); return canvas; } @Override protected FootprintRenderer getThis() { return this; } @Override public List render(final GraphicsContext gc, final Chart chart, final int dataSetOffset, final ObservableList datasets) { if (!(chart instanceof XYChart)) { throw new InvalidParameterException( "must be derivative of XYChart for renderer - " + this.getClass().getSimpleName()); } final XYChart xyChart = (XYChart) chart; // make local copy and add renderer specific data sets final List localDataSetList = new ArrayList<>(datasets); localDataSetList.addAll(super.getDatasets()); long start = 0; if (ProcessingProfiler.getDebugState()) { start = ProcessingProfiler.getTimeStamp(); } final Axis xAxis = xyChart.getXAxis(); final Axis yAxis = xyChart.getYAxis(); final double xAxisWidth = xAxis.getWidth(); final double xmin = xAxis.getValueForDisplay(0); final double xmax = xAxis.getValueForDisplay(xAxisWidth); int index = 0; for (final DataSet ds : localDataSetList) { if (ds.getDimension() < 7) continue; final int lindex = index; ds.lock().readLockGuardOptimistic(() -> { // update categories in case of category axes for the first (index == '0') indexed data set if (lindex == 0 && xyChart.getXAxis() instanceof CategoryAxis) { final CategoryAxis axis = (CategoryAxis) xyChart.getXAxis(); axis.updateCategories(ds); } attrs = null; if (ds instanceof AttributeModelAware) { attrs = (AttributeModelAware) ds; } itemAware = (IOhlcvItemAware) ds; isEpAvailable = !paintAfterEPS.isEmpty() || paintBarMarker != null; gc.save(); // default styling level String style = ds.getStyle(); DefaultRenderColorScheme.setLineScheme(gc, style, lindex); DefaultRenderColorScheme.setGraphicsContextAttributes(gc, style); // footprint settings Font basicFontTemplate = footprintAttrs.getRequiredAttribute(BID_ASK_VOLUME_FONTS)[1]; Font selectedFontTemplate = footprintAttrs.getRequiredAttribute(BID_ASK_VOLUME_FONTS)[2]; // financial styling level pocColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_POC_COLOR, Color.rgb(255, 255, 0)); footprintDefaultFontColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_DEFAULT_FONT_COLOR, Color.rgb(255, 255, 255, 0.58)); footprintCrossLineColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_CROSS_LINE_COLOR, Color.GRAY); footprintBoxLongColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_LONG_COLOR, Color.GREEN); fooprintBoxShortColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_SHORT_COLOR, Color.RED); footprintVolumeLongColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_VOLUME_LONG_COLOR, Color.rgb(139, 199, 194, 0.2)); footprintVolumeShortColor = StyleParser.getColorPropertyValue(style, DATASET_FOOTPRINT_VOLUME_SHORT_COLOR, Color.rgb(235, 160, 159, 0.2)); double barWidthPercent = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_FOOTPRINT_BAR_WIDTH_PERCENTAGE, 0.5d); double positionPaintMainRatio = StyleParser.getFloatingDecimalPropertyValue(style, DATASET_FOOTPRINT_PAINT_MAIN_RATIO, 5.157d); if (ds.getDataCount() > 0) { iMin = ds.getIndex(DIM_X, xmin); if (iMin < 0) iMin = 0; iMax = Math.min(ds.getIndex(DIM_X, xmax) + 1, ds.getDataCount()); distances = null; double minRequiredWidth = 0.0; if (lindex == 0) { distances = findAreaDistances(findAreaDistances, ds, xAxis, yAxis, xmin, xmax); minRequiredWidth = distances[0]; } localBarWidth = minRequiredWidth * barWidthPercent; barWidthHalf = localBarWidth / 2.0; ratio = Math.pow(localBarWidth, 0.25) * positionPaintMainRatio; // calculate ratio depended attributes basicFont = getFontWithRatio(basicFontTemplate, ratio); selectedFont = getFontWithRatio(selectedFontTemplate, ratio); fontGap = getFontGap(5.0, ratio); basicGap = getFontGap(1.0, ratio); FontMetrics metricsBasicFont = getFontMetrics(basicFont); heightText = metricsBasicFont.getLeading() + metricsBasicFont.getAscent(); for (int i = iMin; i < iMax; i++) { double x0 = xAxis.getDisplayPosition(ds.get(DIM_X, i)); // get all additional information for footprints IOhlcvItem ohlcvItem = itemAware.getItem(i); IOhlcvItem lastOhlcvItem = itemAware.getLastItem(); boolean isLastBar = lastOhlcvItem == null || lastOhlcvItem.getTimeStamp().equals(ohlcvItem.getTimeStamp()); if (!footprintRenderedApi.isFootprintAvailable(ohlcvItem)) { continue; } synchronized (footprintRenderedApi.getLock(ohlcvItem)) { drawFootprintItem(gc, yAxis, ds, i, x0, ohlcvItem, isEpAvailable, isLastBar, paintVolume); if (isLastBar && paintPullbackColumn) { IOhlcvItem pullbackColumn = footprintRenderedApi.getPullbackColumn(ohlcvItem); if (pullbackColumn != null) { x0 = x0 + localBarWidth + barWidthHalf; drawFootprintItem(gc, yAxis, ds, i, x0, pullbackColumn, false, true, false); } } } } } gc.restore(); }); // possibility to re-arrange y-axis by min/max of dataset (after paint) if (computeLocalRange()) { applyLocalYRange(ds, yAxis, xmin, xmax); } index++; } if (ProcessingProfiler.getDebugState()) { ProcessingProfiler.getTimeDiff(start); } return localDataSetList; } private void drawFootprintItem(GraphicsContext gc, Axis yAxis, DataSet ds, int i, double x0, IOhlcvItem ohlcvItem, boolean isEpAvailable, boolean isLastBar, boolean paintVolume) { double yOpen = yAxis.getDisplayPosition(ohlcvItem.getOpen()); double yHigh = yAxis.getDisplayPosition(ohlcvItem.getHigh()); double yLow = yAxis.getDisplayPosition(ohlcvItem.getLow()); double yClose = yAxis.getDisplayPosition(ohlcvItem.getClose()); double open = ohlcvItem.getOpen(); double close = ohlcvItem.getClose(); // call api Collection priceVolumeList = footprintRenderedApi.getPriceVolumeList(ohlcvItem); double pocPrice = footprintRenderedApi.getPocPrice(ohlcvItem); NbColumnColorGroup resultColorGroups = footprintRenderedApi.getColumnColorGroup(ohlcvItem); double yDiff = yOpen - yClose; double yMin = yDiff > 0 ? yClose : yOpen; // prepare extension point data (if EPs available) OhlcvRendererEpData data = null; if (isEpAvailable) { data = new OhlcvRendererEpData(); data.gc = gc; data.ds = ds; data.attrs = attrs; data.ohlcvItemAware = itemAware; data.ohlcvItem = ohlcvItem; data.index = i; data.minIndex = iMin; data.maxIndex = iMax; data.barWidth = localBarWidth; data.barWidthHalf = barWidthHalf; data.xCenter = x0; data.yOpen = yOpen; data.yHigh = yHigh; data.yLow = yLow; data.yClose = yClose; data.yDiff = yDiff; data.yMin = yMin; } // paint volume if (paintVolume) { assert distances != null; paintVolume(gc, ds, i, footprintVolumeLongColor, footprintVolumeShortColor, yAxis, distances, localBarWidth, barWidthHalf, x0); } // choose color of the bar boxes (left part of the footprint) Paint barPaint = null; if (data != null) { barPaint = getPaintBarColor(data); } // draw footprint chart // draw cross-line gc.setStroke(footprintCrossLineColor); gc.strokeLine(x0, yHigh - heightText / 2.0, x0, yLow + heightText / 2.0); // draw bid-ask rows double maxWidthTextBid = -Double.MAX_VALUE; for (Double[] priceVolume : priceVolumeList) { double price = priceVolume[0]; double bidVolume = priceVolume[1]; double askVolume = priceVolume[2]; boolean isLastBarAndLastPrice = isLastBar && price == close; double widthTextBidBasic = computeTextWidth(basicFont, getFormattedVolume(bidVolume), 0); double widthTextBidSelected = computeTextWidth(selectedFont, getFormattedVolume(bidVolume), 0); double widthTextAskBasic = computeTextWidth(basicFont, getFormattedVolume(askVolume), 0); double widthTextAskSelected = computeTextWidth(selectedFont, getFormattedVolume(askVolume), 0); double widthTextBid = isLastBarAndLastPrice ? widthTextBidSelected : widthTextBidBasic; double widthTextAsk = isLastBarAndLastPrice ? widthTextAskSelected : widthTextAskBasic; if (widthTextBidBasic > maxWidthTextBid) maxWidthTextBid = widthTextBidBasic; double xxBid = x0 - widthTextBid - fontGap; double xxAsk = x0 + fontGap; double bidAskVolumeY = yAxis.getDisplayPosition(price) + heightText / 2.0; // center of text to price value // paint POC rectangle if (paintPoc && price == pocPrice) { gc.setStroke(pocColor); gc.setLineCap(StrokeLineCap.BUTT); gc.setLineJoin(StrokeLineJoin.MITER); gc.setMiterLimit(10.0f); gc.setLineWidth(1.5f); gc.strokeRect(x0 - widthTextBid - fontGap - 2.0 * basicGap, bidAskVolumeY - heightText - basicGap, widthTextBid + widthTextAsk + 2.0 * fontGap + 2.0 * basicGap, heightText + 4.0 * basicGap); } // paint area bid/ask text description if (resultColorGroups != null) { // color and font palette of numbers bars FontColor fontColor = resultColorGroups.fontColorMap.get(price); gc.setFont(isLastBarAndLastPrice ? selectedFont : fontColor.bidFont); gc.setFont(new Font(calcFontSize(gc.getFont().getSize(), ratio))); gc.setFill(fontColor.bidColor); gc.fillText(getFormattedVolume(bidVolume), xxBid, bidAskVolumeY); gc.setFont(isLastBarAndLastPrice ? selectedFont : fontColor.askFont); gc.setFont(new Font(calcFontSize(gc.getFont().getSize(), ratio))); gc.setFill(fontColor.askColor); gc.fillText(getFormattedVolume(askVolume), xxAsk, bidAskVolumeY); } else { gc.setFont(isLastBarAndLastPrice ? selectedFont : basicFont); gc.setFill(footprintDefaultFontColor); gc.fillText(getFormattedVolume(bidVolume), xxBid, bidAskVolumeY); gc.fillText(getFormattedVolume(askVolume), xxAsk, bidAskVolumeY); } } // for // paint body box indicator for (Double[] priceVolume : priceVolumeList) { double price = priceVolume[0]; double bidAskVolumeY = yAxis.getDisplayPosition(price) + heightText / 2.0; if ((close > open && price >= open && price <= close) || (close <= open && price <= open && price >= close)) { gc.setLineWidth(1.0f); if (close > open) { if (barPaint != null) { gc.setFill(barPaint); } else { gc.setFill(footprintBoxLongColor); } } else { if (barPaint != null) { gc.setFill(barPaint); } else { gc.setFill(fooprintBoxShortColor); } } gc.fillRect(x0 - maxWidthTextBid - fontGap - 10.0 * basicGap, bidAskVolumeY - heightText, 4.0 * basicGap, heightText); } } // extension point - paint after footprint painting if (isEpAvailable) { // renderer EP extension data EpDataAddon epDataAddon = new EpDataAddon(); epDataAddon.basicGap = basicGap; epDataAddon.fontGap = fontGap; epDataAddon.heightText = heightText; epDataAddon.maxWidthTextBid = maxWidthTextBid; data.addon = epDataAddon; paintAfter(data); } } //-------------- helpers ------------------ private String getFormattedVolume(double askVolume) { return String.format("%1.0f", askVolume); } private Font getFontWithRatio(Font fontTemplate, double ratio) { return Font.font(fontTemplate.getFamily(), FontWeight.findByName(fontTemplate.getStyle()), calcFontSize(fontTemplate.getSize(), ratio)); } private double calcFontSize(double size, double ratio) { return size / FONT_RATIO * ratio; } private double getFontGap(double gap, double ratio) { return gap / FONT_RATIO * ratio; } private FontMetrics getFontMetrics(Font font) { return fontLoader.getFontMetrics(font); } /** * Handle extension point PaintAfter * * @param data filled domain object which is provided to external extension points. */ protected void paintAfter(OhlcvRendererEpData data) { for (RendererPaintAfterEP paintAfterEP : paintAfterEPS) { paintAfterEP.paintAfter(data); } } //-------------- API ------------------ /** * API Footprint Service * Service provides additional footprint data for each ohlcv item which has to be painted. */ public interface IFootprintRenderedAPI { // Check if the footprint is available for this OHLCV item data boolean isFootprintAvailable(IOhlcvItem ohlcvItem); // Footprint configuration attributes FootprintRendererAttributes getFootprintAttributes(); // list of price, ask, bid values per row Collection getPriceVolumeList(IOhlcvItem ohlcvItem); // get POC price (Point of control) double getPocPrice(IOhlcvItem ohlcvItem); // column font and colors for each NP value NbColumnColorGroup getColumnColorGroup(IOhlcvItem ohlcvItem); // try get pullback column (if the feature is active) IOhlcvItem getPullbackColumn(IOhlcvItem ohlcvItem); // get lock for synch between data consolidation and painting process Object getLock(IOhlcvItem ohlcvItem); } // painting additional data for extension points public static class EpDataAddon { public double heightText; // height of the row public double fontGap; // font gap from cross line to ask/bid number public double basicGap; // basic smallest gap for spacing (calculated with ratio) public double maxWidthTextBid; // maximal text with for bid number (left side of bar) } //-------------- injections -------------------------------------------- @Override public void addPaintAfterEp(RendererPaintAfterEP paintAfterEP) { paintAfterEPS.add(paintAfterEP); } @Override public List getPaintAfterEps() { return paintAfterEPS; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy