/* * ------------------------------------------------------------------------- * $Id: ChartStock.java,v 1.10 2004/05/26 19:20:42 estewart Exp $ * ------------------------------------------------------------------------- * Copyright (c) 1999 Visual Numerics Inc. All Rights Reserved. * * This software is confidential information which is proprietary to * and a trade secret of Visual Numerics, Inc. Use, duplication or * disclosure is subject to the terms of an appropriate license * agreement. * * VISUAL NUMERICS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. VISUAL * NUMERICS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR * ITS DERIVATIVES. *-------------------------------------------------------------------------- */ package com.imsl.demo.WallStreet; import com.imsl.chart.*; import com.imsl.math.Sfun; import java.awt.Font; import java.text.*; import java.util.*; /** * * @author brophy * @created January 24, 2002 */ class ChartStock extends JFrameChart { static private final long DAY = 3600*24*1000; static private final String GRID_COLOR = "lightGray"; static private final DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yy"); private Model model; private AxisXY axis; private Axis1D axisX, axisY, axisVolumeX, axisVolumeY; private Data data; private AxisXY axisVolume; private Data dataVolume; private Data dataOver; private String titleOver; ChartStock(String ticker[], Model model) { super(); setTitle("Stock Price Charting"); Chart chart = getChart(); chart.getChartTitle().setFont(new Font("Serif",Font.BOLD|Font.ITALIC,14)); chart.getBackground().setFillColor("lightYellow"); chart.setTextColor("darkBlue"); axis = new AxisXY(chart); axisX = axis.getAxisX(); axisY = axis.getAxisY(); axisX.getAxisLabel().setTextFormat(DATE_FORMAT); TransformDate transform = new TransformDate(); axisX.setCustomTransform(transform); axisX.setTransform(AxisXY.TRANSFORM_CUSTOM); axisX.setSkipWeekends(true); axisY.getAxisTitle().setTitle("Price"); axisX.getGrid().setPaint(true); axisY.getGrid().setPaint(true); axisX.getGrid().setLineColor(GRID_COLOR); axisY.getGrid().setLineColor(GRID_COLOR); axisVolume = new AxisXY(chart); axisVolumeX = axisVolume.getAxisX(); axisVolumeY = axisVolume.getAxisY(); TransformDate transformVolume = new TransformDate(); axisVolumeX.setCustomTransform(transformVolume); axisVolumeX.setTransform(AxisXY.TRANSFORM_CUSTOM); axisVolumeX.setSkipWeekends(true); axisVolumeX.getAxisLabel().setTextFormat(DATE_FORMAT); axisVolumeX.getAxisLabel().setPaint(false); axisVolumeY.getAxisLabel().setTextFormat("0"); axisVolumeX.getMinorTick().setPaint(false); axisVolumeX.getGrid().setPaint(true); axisVolumeX.getGrid().setLineColor(GRID_COLOR); axisVolumeY.getGrid().setLineColor(GRID_COLOR); axisVolumeX.setAutoscaleInput(Axis1D.AUTOSCALE_OFF); this.model = model; model.addListener(new Model.Listener() { public void modelChanged() { drawChart(); } }); } private void setChartTitle() { int style = model.getStyle(); int period = model.getPeriod(); String sPeriod = ""; switch (period) { case GregorianCalendar.MONTH: sPeriod = "(Monthly)"; break; case GregorianCalendar.WEEK_OF_YEAR: sPeriod = "(Weekly)"; break; case GregorianCalendar.DAY_OF_WEEK: sPeriod = "(Daily)"; break; } String sFormat = ""; switch (style) { case Model.STYLE_CLOSE: sFormat = "Closing Prices for {0} {1}"; break; case Model.STYLE_CANDLESTICK: sFormat = "Candlesticks of {0} {1}"; break; case Model.STYLE_HIGH_LOW_CLOSE: sFormat = "High-Low-Close-Open of {0} {1}"; break; } MessageFormat format = new java.text.MessageFormat(sFormat); Object args[] = new Object[]{model.getTicker(), sPeriod}; getChart().getChartTitle().setTitle(format.format(args)); } private void drawChart() { try { Database database = model.getDatabase(); String ticker = model.getTicker(); int style = model.getStyle(); setChartTitle(); Database.Series series = database.getSeries(ticker, model.getPeriod(), model.getInterval()); init(series); switch (style) { case Model.STYLE_CLOSE: plotClose(ticker, series); break; case Model.STYLE_CANDLESTICK: plotCandlestick(ticker, series); break; case Model.STYLE_HIGH_LOW_CLOSE: plotHighLowClose(ticker, series); break; } int over = model.getOverlay(); if (series.date.length == 0) over = Model.OVERLAY_NONE; double[] overlay; Chart chart = getChart(); switch (over) { case Model.OVERLAY_NONE: chart.getLegend().setPaint(false); break; case Model.OVERLAY_SAR: overlay = computeSAR(series); titleOver = "Parabolic SAR"; chart.getLegend().setPaint(true); plotOverlay(ticker, series, overlay); break; case Model.OVERLAY_MA20: overlay = computeMA20(series); titleOver = "20 pt Moving Avg"; chart.getLegend().setPaint(true); plotOverlay(ticker, series, overlay); break; case Model.OVERLAY_KAL: overlay = computeKalman(series); titleOver = "Kalman Filter"; chart.getLegend().setPaint(true); plotOverlay(ticker, series, overlay); break; } chart.getLegend().setViewport(0.8,0.9,0.04,0.09); repaint(); } catch (Exception e) { e.printStackTrace(); } } private double[] computeSAR(Database.Series series) { // set the constants final double a0 = 0.00; // should be 0.02, but always incremented, so 0.00 final double delta_a = 0.02; final double max_a = 0.20; // declare the variables double a = a0; boolean lon = true; // long (true) or short (false) double ep; // extreme point double[] s = new double[series.close.length]; if (series.close[1] >= series.close[0]) { lon = true; ep = series.high[0]; } else { lon = false; ep = series.low[0]; } s[0] = series.close[0]; double maxhi = series.high[0]; double minlo = series.low[0]; for (int i=1; i<series.close.length; i++) { if (series.high[i] > maxhi) { maxhi = series.high[i]; } if (series.low[i] < minlo) { minlo = series.low[i]; } // test for reversal if ((lon) && (series.close[i] < s[i-1])) { lon = false; // we're now short a = a0; // reset acceleration factor ep = maxhi; // new extreme point s[i] = ep; continue; } if ((!lon) && (series.close[i] > s[i-1])) { lon = true; // we're now long a = a0; // reset acceleration factor ep = minlo; // new extreme point s[i] = ep; continue; } // test to increase acceleration if ((lon) && (series.high[i] > ep)) { a += delta_a; if (a > max_a) a = max_a; ep = series.high[i]; } if ((!lon) && (series.low[i] < ep)) { a += delta_a; if (a > max_a) a = max_a; ep = series.low[i]; } // compute new SAR s[i] = s[i-1] + a*(ep-s[i-1]); // s cannot enter trading range of last 2 periods if (lon) { if (s[i] > series.low[i]) s[i] = series.low[i]; if (s[i] > series.low[i-1]) s[i] = series.low[i-1]; } if (!lon) { if (s[i] < series.high[i]) s[i] = series.high[i]; if (s[i] < series.high[i-1]) s[i] = series.high[i-1]; } } return s; } private double[] computeMA20(Database.Series series) { final int period = 20; int num = 0; double sum = 0.0; double[] m = new double[series.close.length]; for (int i=0; i<series.close.length; i++) { sum += series.close[i]; if (i < period) { num++; m[i] = sum/num; } else { sum -= series.close[i-period]; m[i] = sum/period; } } return m; } private double[] computeKalman(Database.Series series) { int nobs = series.close.length; double[] ff = new double[nobs]; int rank = 0; double logDeterminant = 0.0; double ss = 0.0; double[] b = {4}; double[] covb = {16}; double[][] q = {{1}}; double[][] r = {{1}}; double[][] t = {{1}}; double[][] z = {{1}}; q[0][0] = 0.1; for (int i = 0; i < nobs; i++) { double yi[] = {series.close[i]}; com.imsl.stat.KalmanFilter kalman = new com.imsl.stat.KalmanFilter(b, covb, rank, ss, logDeterminant); kalman.update(yi, z, r); kalman.filter(); b = kalman.getStateVector(); covb = kalman.getCovB(); rank = kalman.getRank(); ss = kalman.getSumOfSquares(); logDeterminant = kalman.getLogDeterminant(); double v[] = kalman.getPredictionError(); double covv[][] = kalman.getCovV(); kalman = new com.imsl.stat.KalmanFilter(b, covb, rank, ss, logDeterminant); kalman.setTransitionMatrix(t); kalman.setQ(q); kalman.filter(); b = kalman.getStateVector(); covb = kalman.getCovB(); rank = kalman.getRank(); ss = kalman.getSumOfSquares(); logDeterminant = kalman.getLogDeterminant(); ff[i] = b[0]; } return ff; } private void plotOverlay(String ticker, Database.Series series, double[] overlay) { dataOver = new Data(axis, series.date, overlay); dataOver.setDataType(Data.DATA_TYPE_MARKER); dataOver.setMarkerType(Data.MARKER_TYPE_FILLED_DIAMOND); dataOver.setMarkerColor("black"); dataOver.setMarkerSize(0.4); dataOver.setTitle(titleOver); repaint(); } private void plotClose(String ticker, Database.Series series) { data = new Data(axis, series.date, series.close); data.setLineColor("blue"); plotVolume(series.date, series.volume); } private void plotCandlestick(String ticker, Database.Series series) { data = new Candlestick(axis, series.date, series.high, series.low, series.close, series.open); data.setMarkerSize(Math.min(1,30./series.date.length)); ((Candlestick)data).getUp().setFillColor("green"); ((Candlestick)data).getDown().setFillColor("red"); plotVolume(series.date, series.volume); } private void plotHighLowClose(String ticker, Database.Series series) { data = new HighLowClose(axis, series.date, series.high, series.low, series.close, series.open); data.setMarkerSize(Math.min(1.0,30./series.date.length)); data.setMarkerColor("blue"); plotVolume(series.date, series.volume); } private void plotVolume(double date[], double volume[]) { if (dataVolume != null) dataVolume.remove(); if (dataOver != null) dataOver.remove(); int volumeStyle = model.getVolumeStyle(); if (volumeStyle != Model.VOLUME_NONE) { axis.setViewport(0.1, 0.95, 0.1, 0.65); axisVolume.setViewport(0.1, 0.95, 0.725, 0.95); axisVolume.setPaint(true); } else { axis.setViewport(0.1, 0.95, 0.1, 0.95); axisVolume.setPaint(false); dataVolume = null; return; } double v[] = (double[])volume.clone(); double max = 0.0; for (int k = 0; k < v.length; k++) max = Math.max(max,v[k]); double scale; String label; if (max > 3.0e6) { scale = 1.0e-6; label = "Millions"; } else { scale = 1.0e-3; label = "Thousands"; } for (int k = 0; k < v.length; k++) v[k] *= scale; axisVolumeY.getAxisTitle().setTitle(label); // Copy scaling parameters from from price chart to the volume chart axis.setupMapping(); axisVolumeX.setWindow(axisX.getWindow()); axisVolumeX.setTicks(axisX.getTicks()); axisVolume.setupMapping(); if (v.length == 0) { } else if (volumeStyle == Model.VOLUME_BARS) { dataVolume = new Bar(axisVolume, date, v); dataVolume.setBarType(Bar.BAR_TYPE_VERTICAL); double win[] = axisVolumeX.getWindow(); double width = 0.25*(win[1]-win[0])/Math.max(10,v.length); if (v.length > 100) width = 0; dataVolume.setBarWidth(width); dataVolume.setFillColor("blue"); dataVolume.setFillOutlineType(Data.FILL_TYPE_NONE); } else if (volumeStyle == Model.VOLUME_LINE) { dataVolume = new Data(axisVolume, date, v); dataVolume.setDataType(Data.DATA_TYPE_LINE); dataVolume.setLineColor("blue"); } else if (volumeStyle == Model.VOLUME_AREA) { dataVolume = new Data(axisVolume, date, v); dataVolume.setDataType(Data.DATA_TYPE_FILL); dataVolume.setFillColor("blue"); } } private void init(Database.Series series) { if (data != null) data.remove(); int scaling = model.getScaling(); int transform = Data.TRANSFORM_LINEAR; if (scaling == Model.SCALING_LOG) transform = Data.TRANSFORM_LOG; axisY.setTransform(transform); if (series.close.length == 0) { axisY.setWindow(0, 1); axisY.setAutoscaleInput(AxisXY.AUTOSCALE_OFF); } else if (scaling == Model.SCALING_LOG) { double min = series.low[0]; double max = series.high[0]; for (int k = 1; k < series.low.length; k++) { min = Math.min(min,series.low[k]); max = Math.max(max,series.high[k]); } int logMin = (int)Math.floor(Sfun.log10(min)); int logMax = (int)Math.ceil(Sfun.log10(max)); min = Math.pow(10.0, logMin); max = Math.pow(10.0, logMax); double ticks[] = new double[5]; double f = max/ticks.length; ticks[0] = min; for (int k = 0; k < ticks.length; k++) { ticks[k] = (k+1)*f; } axisY.setTicks(ticks); axisY.setWindow(min,max); axisY.setAutoscaleInput(AxisXY.AUTOSCALE_OFF); } else { axisY.setAttribute("Ticks", null); axisY.setAutoscaleInput(AxisXY.AUTOSCALE_DATA); axisY.setAutoscaleOutput(AxisXY.AUTOSCALE_WINDOW | AxisXY.AUTOSCALE_NUMBER); } } }