Stock Example
Graphs the historical prices of a stock.
This example shows most of the Webgraph API in the context of a realistic application, such as graphing the historical prices of a stock.
back to index
The source code:
|
/**
* @version $Id: StockExample.java,v 1.2 2000/11/13 18:47:17 Administrator Exp $
*/
import com.smartmoney.webgraph.*;
import com.smartmoney.webgraph.stocks.*;
import com.smartmoney.webgraph.stats.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
/**
* StockExample
*
* A sample application that exercises the important parts of the
* webgraph public interface. We create several GraphElements which
* share the same GraphModel; we provide hiding and showing of graph
* elements; we show two Graphs simultaneously, and have them
* respond to each other's mouse events; we allow interactive panning
* of the visible data set via a scrollbar; we show the use of
* Graph.setNormalize(); we show the use of logarithmic and
* linear scales on the Y axis; we use different Formatters; we show
* the use of autoscaling and when to turn it off.
*
* @author <a href="mailto:jdf@pobox.com">Jonathan Feinberg</a>
*/
public class StockExample
extends Applet
implements ItemListener, AdjustmentListener, LegendListener, StockConstants
{
static private final int ONE_MONTH = 5 * 4; // 20 business days
static private final int ONE_YEAR = 52 * 5;
// How many days of stock data do we want?
static private final int DAYS = ONE_YEAR * 5; // 5 business years
static private final int INITIAL_VIEW = ONE_YEAR;
// What color do we give our main stock graph elements
static private final Color mainColorDefault = Color.blue;
// The "g" panel shows the stock prices; "v" shows the share
// volume. We can't display both in the same Graph because
// they have different scales (typically, prices are on the order
// of 10^2 and are displayed in dollars, while volume is on the
// order of 10^6 and displayed in counting numbers).
BusinessDateGraph priceGraph, volumeGraph;
MovingAverageFilter maFilter;
// The Graphs work by managing any number of graph elements.
GraphElement line, hlc, candle, dividends, splits;
GraphElement regression, movingAverage, bollinger;
GraphElement volume, onBalanceVolume, moneyFlowIndex;
StochasticOscillatorElement soK;
// This object encapsulates raw data for an imaginary stock
// instrument over some arbitrary number of days. In a real-world
// stock-graphing application, you'd need something like this that
// actually retrieves stock data instead of making it up at
// random.
FakeStock mainFakeStock;
String mainName;
// Some other "instruments" to compare to our main stock
String[] stockNames = new String[]{"MSFT", "T", "ATT", "C"};
Color[] stockColors = new Color[]{Color.red, Color.green,
Color.orange, Color.magenta};
// We will keep track of the graph elements for the competitor
// stocks in an array. The LegendPanel objects allow the user to
// show and hide individual elements (they are simply a checkbox
// with a color square).
GraphElement[] stocks;
LegendPanel[] legends;
// We use a Choice to select the currently visible time range
Choice cRange;
// We use a Choice to select the desired stock analysis
Choice cAnalysis;
// We use checkboxes to control which view of the main stock data
// is active (simple line graph, HLC bars, or candles).
Checkbox cbLine, cbHLC, cbCandle;
// A checkbox to switch between log and linear scales
Checkbox cbLog;
// We use a scrollbar to control panning over the entire range of
// the graph when viewing a subset. See adjustmentValueChanged(),
// below, for implementation details.
Scrollbar panner;
public void init()
{
ParameterParser parser = new ParameterParser(this, true);
for (int i = 0; i < stockNames.length; i++)
{
String param = "stock" + (i + 1);
stockColors[i] = parser.getColor(param + "color", stockColors[i]);
stockNames[i] = parser.getString(param + "name", stockNames[i]);
}
setBackground(Color.white);
// Create the main performance graph
priceGraph = new BusinessDateGraph();
// Create the volume graph.
volumeGraph = new BusinessDateGraph();
// And the control panel
Panel controls = new Panel();
//controls.setLayout(new FlowLayout(FlowLayout.LEFT));
controls.setLayout(new GridLayout(0, 1));
controls.setBackground(Color.white);
// We always want to graph the volume with 0 at the bottom,
// even though the volume will never go that low.
volumeGraph.setGraphMinimum(0);
// We don't want the volume graph to rescale itself when the
// range of visible Y values changes through panning or
// zooominpriceGraph.
volumeGraph.setAutoScale(false);
// We want each graph to display callouts even when the mouse
// is moving in the other graph.
priceGraph.addCalloutListener(volumeGraph);
volumeGraph.addCalloutListener(priceGraph);
// By default, a graph displays Y values as if they were
// double-precision floating point numbers. But the top graph
// is in dollars, and the bottom graph is in whole numbers
// (shares sold).
String currencyFormatString = getParameter("currencyformat");
if (currencyFormatString == null)
priceGraph.setYFormatter(Formatters.CURRENCY);
else
priceGraph.setYFormatter(currencyFormatString);
volumeGraph.setYFormatter(Formatters.PLAIN);
// We'll display the date on the top graph only.
volumeGraph.setDrawXValue(false);
volumeGraph.setDrawXLabels(false);
volumeGraph.setDrawYLabels(false);
// This line of code represents whatever you have to do to
// retrieve information about a given instrument. Here we use
// a utility class that generates a reasonable-looking random
// stock history.
mainFakeStock = new FakeStock(DAYS);
// Create a StockModel that we can render with various
// GraphElements. Elements render models. Several elements
// can share the same model, which makes the Graph fairly
// efficient. This is a StockModel (which provides various
// prices per date); later, we'll use some regular GraphModels.
StockDataSource mainData
= new StockDataSource(mainFakeStock.dates,
mainFakeStock.open,
mainFakeStock.high,
mainFakeStock.low,
mainFakeStock.close,
mainFakeStock.volume);
DataSource priceData = new StockPriceFilter(mainData);
DataSource volumeData = new StockVolumeFilter(mainData);
GraphModel priceModel = new GraphModel(priceData);
// Create several different elements, each of which emphasizes
// a different aspect of the StockModel.
Color mainColor = parser.getColor("mainstockcolor", mainColorDefault);
mainName = parser.getString("mainstockname", "UBM");
// A simple line to show the close price.
line = new LineElement(priceModel, CLOSE);
line.setColor(mainColor);
// setLabel() puts an informative string into the element's callout
line.setLabel(mainName);
// We will only show one view of the model at at time.
line.setVisible(false);
// Now add it to the graph.
priceGraph.addGraphElement(line);
// A High/Low/Close graph element.
hlc = new HLCElement(priceModel);
hlc.setLabel(mainName);
hlc.setColor(mainColor);
priceGraph.addGraphElement(hlc);
// A candle element (indicating high, low, open, close)
candle = new CandleElement(priceModel);
candle.setLabel(mainName);
candle.setColor(mainColor);
candle.setVisible(false);
priceGraph.addGraphElement(candle);
// Here we show dividends and splits by creating "annotation
// models", models that refers to another model. In the
// following construction, for example, we're saying "create a
// model that annotates the ubmModel model; for each date in
// the mainFakeStock.divDates array, annotate the corresponding data
// point in ubmModel with the corresponding string in
// mainFakeStock.divDescriptions."
AnnotationDataFilter adf
= new AnnotationDataFilter(mainData,
CLOSE,
mainFakeStock.divDates,
mainFakeStock.divDescriptions,
AnnotationDataFilter.REJECT);
AnnotationModel dm = new AnnotationModel(adf, priceModel);
// The LetterAnnotationElement renders the AnnotationModel by
// drawing a letter inside a cirle.
dividends = new LetterAnnotationElement(dm, "D");
dividends.setColor(mainColor);
dividends.setLabel(mainName + " Dividend:");
priceGraph.addGraphElement(dividends);
// we'll only show it when the line element is visible
dividends.setVisible(false);
AnnotationDataFilter sdf
= new AnnotationDataFilter(mainData,
CLOSE,
mainFakeStock.splitDates,
mainFakeStock.splitDescriptions,
AnnotationDataFilter.REJECT);
AnnotationModel sm = new AnnotationModel(sdf, priceModel);
splits = new LetterAnnotationElement(sm, "S");
splits.setColor(mainColor);
splits.setLabel(mainName + " Splits");
splits.setVisible(false);
priceGraph.addGraphElement(splits);
// Now some fancy analysis graph elements
regression = new LineElement(new RegressionModel(priceData, priceModel), CLOSE);
regression.setColor(Color.gray);
regression.setLabel(mainName + " Regression");
regression.setVisible(false);
priceGraph.addGraphElement(regression);
maFilter = new MovingAverageFilter(priceData, 20);
movingAverage = new LineElement(new GraphModel(maFilter, priceModel), CLOSE);
movingAverage.setColor(Color.gray);
movingAverage.setLabel(mainName + " 20-Day");
movingAverage.setVisible(false);
priceGraph.addGraphElement(movingAverage);
bollinger = new BollingerBandsElement
(new GraphModel(new BollingerBandsFilter(mainData), priceModel));
bollinger.setColor(mainColor); // for the callout
bollinger.setLabel(mainName);
bollinger.setVisible(false);
priceGraph.addGraphElement(bollinger);
// For the volume graph, we'll make a "shadow" element.
volume = new ShadowElement(new GraphModel(volumeData));
volume.setColor(mainColor);
volume.setLabel(mainName + " Volume:");
volumeGraph.addGraphElement(volume);
onBalanceVolume = new LineElement(new GraphModel
(new OnBalanceVolumeFilter(mainData)));
onBalanceVolume.setColor(mainColor);
onBalanceVolume.setLabel("OBV");
onBalanceVolume.setVisible(false);
volumeGraph.addGraphElement(onBalanceVolume);
moneyFlowIndex = new LineElement(new GraphModel
(new MoneyFlowIndexFilter(mainData)));
moneyFlowIndex.setColor(mainColor);
moneyFlowIndex.setLabel("MFI");
moneyFlowIndex.setVisible(false);
volumeGraph.addGraphElement(moneyFlowIndex);
GraphModel soModel =
new GraphModel
(new StochasticOscillatorFilter(mainData, 14, 3));
soK = new StochasticOscillatorElement(soModel,
StochasticOscillatorFilter.PERCENT_D, StochasticOscillatorFilter.PERCENT_K);
soK.setKPeriodColor(Color.black);
soK.setDPeriodColor(Color.red);
soK.setLabel("Stochastic Osc.");
soK.setVisible(false);
volumeGraph.addGraphElement(soK);
// Create the line elements for the "competition" stocks.
stocks = new GraphElement[stockNames.length];
for (int i = 0; i < stocks.length; i++)
{
FakeStock f = new FakeStock(DAYS);
stocks[i] = new LineElement
(new GraphModel
(new SimpleDataSource(f.dates, f.close)));
stocks[i].setColor(stockColors[i]);
stocks[i].setLabel(stockNames[i]);
stocks[i].setVisible(false);
priceGraph.addGraphElement(stocks[i]);
}
// Here's a scrollbar that will control panning
panner = new Scrollbar(Scrollbar.HORIZONTAL,
DAYS - INITIAL_VIEW, INITIAL_VIEW,
0, DAYS);
panner.addAdjustmentListener(this);
setGraphViews(DAYS - INITIAL_VIEW, INITIAL_VIEW);
// Now some controls
cbLog = new Checkbox("Use Log Scale");
cbLog.addItemListener(this);
controls.add(cbLog);
controls.add(new Label("Time Period:"));
cRange = new Choice();
cRange.add("One Month");
cRange.add("Three Months");
cRange.add("Six Months");
cRange.add("One Year");
cRange.add("Five Years");
cRange.addItemListener(this);
controls.add(cRange);
cRange.select(3);
controls.add(new Label("Analysis:"));
cAnalysis = new Choice();
cAnalysis.add("No Analysis");
cAnalysis.add("Regression Line");
cAnalysis.add("20 Day Average");
cAnalysis.add("50 Day Average");
cAnalysis.add("Bollinger Bands");
cAnalysis.add("On-Balance Volume");
cAnalysis.add("Money Flow Index");
cAnalysis.add("Stochastic Oscillator");
cAnalysis.addItemListener(this);
controls.add(cAnalysis);
controls.add(new Spacer(100, 10));
controls.add(new Label("View " + mainName + " as:"));
CheckboxGroup cbg = new CheckboxGroup();
cbLine = new Checkbox("Line", cbg, true);
cbLine.addItemListener(this);
controls.add(cbLine);
cbHLC = new Checkbox("High/Low/Close", cbg, false);
cbHLC.addItemListener(this);
controls.add(cbHLC);
cbCandle = new Checkbox("Candle", cbg, false);
cbCandle.addItemListener(this);
controls.add(cbCandle);
// Start with HLC graph showing
cbHLC.setState(true);
controls.add(new Spacer(100, 10));
controls.add(new Label("Compare " + mainName + " to:"));
legends = new LegendPanel[stocks.length];
for (int i = 0; i < stocks.length; i++)
{
legends[i] =
new LegendPanel(stockColors[i], stockNames[i], (LegendListener) this);
controls.add(legends[i]);
}
// Layout the applet
int h = getSize().height;
int w = getSize().width;
int gw = (int) (w * .75);
int gh = (int) (h * .75);
priceGraph.setSize(new Dimension(gw, gh));
volumeGraph.setSize(new Dimension(gw, h - gh));
Panel graphs = new Panel();
graphs.setLayout(new BorderLayout());
graphs.add(priceGraph, BorderLayout.CENTER);
graphs.add(volumeGraph, BorderLayout.SOUTH);
Panel pannedGraphs = new Panel();
pannedGraphs.setLayout(new BorderLayout());
pannedGraphs.add(graphs, BorderLayout.CENTER);
pannedGraphs.add(panner, BorderLayout.SOUTH);
setLayout(null);
add(pannedGraphs);
add(controls);
pannedGraphs.setBounds(0, 0, gw, h);
controls.setBounds(gw + 4, 0, w - gw - 4, h);
GraphParameterParser.parse(priceGraph, parser);
GraphParameterParser.parse(volumeGraph, parser);
setRange(INITIAL_VIEW - 1);
}
// Here we hide and show the various views of the ubmModel based
// on the state of some checkboxes. We only show the dividends
// and splits if the line element is selected.
private void elementTypeSelected(Checkbox c)
{
dividends.setVisible(c == cbLine || c == cbHLC);
splits.setVisible(c == cbLine || c == cbHLC);
line.setVisible(c == cbLine);
hlc.setVisible(c == cbHLC);
candle.setVisible(c == cbCandle);
}
// This method shows the use of the Graph's setView() method,
// which allows you to control the range of x values currently
// visible. The X values provided do not necessarily have to
// correspond to values that appear in the X data, but this
// implementation is simple and winds up looking good, too.
private void setGraphViews(int startIndex, int range)
{
Date start = mainFakeStock.dates[startIndex];
Date end = mainFakeStock.dates[startIndex + range - 1];
priceGraph.setView(start, end);
volumeGraph.setView(start, end);
}
// Given a range (i.e., the number of business days visible in the
// graphs), set up the panner scrollbar to reflect the new range,
// and make sure the graph still looks good.
private void setRange(int range)
{
// Choose a harmonious X label format
if (range < ONE_MONTH * 6)
priceGraph.setXLabelFormatter(Formatters.LONG_DATE);
else if (range < ONE_YEAR * 5)
priceGraph.setXLabelFormatter(Formatters.MONTH_YEAR);
else
priceGraph.setXLabelFormatter(Formatters.YEAR);
// We only wish to allow the display of Candle and HLC
// graph elements below certain ranges, since they become
// illegible when viewing many dates at once.
if (range > ONE_MONTH * 6 && !cbLine.getState())
{
cbLine.setState(true);
elementTypeSelected(cbLine);
} else if (range > ONE_MONTH * 3 && cbCandle.getState())
{
cbHLC.setState(true);
elementTypeSelected(cbHLC);
}
cbHLC.setEnabled(range <= ONE_MONTH * 6);
cbCandle.setEnabled(range <= ONE_MONTH * 3);
int start = panner.getValue();
if (start + range >= DAYS)
start -= start + range - DAYS;
panner.setValues(start, range, 0, DAYS);
setGraphViews(start, range);
}
public void itemStateChanged(ItemEvent e)
{
if (e.getItemSelectable() instanceof Checkbox)
{
Checkbox cb = (Checkbox) e.getItemSelectable();
if (cb == cbLog)
{
// Here's how you tell a Graph to use logarithmic
// Y scale (and how you tell it to switch back to
// linear scale).
if (cb.getState())
priceGraph.useLogScale();
else
priceGraph.useLinearScale();
} else
elementTypeSelected(cb);
} else if (e.getItemSelectable() instanceof Choice)
{
Choice c = (Choice) e.getItemSelectable();
int n = c.getSelectedIndex();
if (c == cRange)
{
if (n == 0)
setRange(ONE_MONTH);
else if (n == 1)
setRange(ONE_MONTH * 3);
else if (n == 2)
setRange(ONE_MONTH * 6);
else if (n == 3)
setRange(ONE_YEAR);
else if (n == 4) setRange(ONE_YEAR * 5);
} else if (c == cAnalysis)
{
volume.setVisible(!(n >= 5));
regression.setVisible(n == 1);
movingAverage.setVisible(n == 2 || n == 3);
bollinger.setVisible(n == 4);
onBalanceVolume.setVisible(n == 5);
moneyFlowIndex.setVisible(n == 6);
soK.setVisible(n == 7);
boolean volumeAnalysis = (n >= 5);
volumeGraph.setDrawYLabels(volumeAnalysis);
volumeGraph.setAutoScale(volumeAnalysis);
volumeGraph.unsetGraphMinimum();
volumeGraph.unsetGraphMaximum();
if (n == 7 || !volumeAnalysis)
volumeGraph.setGraphMinimum(0);
if (n == 7)
volumeGraph.setGraphMaximum(1);
volumeGraph.setYFormatter(((n == 6) || (n == 7)) ?
Formatters.PERCENT : Formatters.PLAIN);
if (n == 2 || n == 3)
{
movingAverage.setLabel(mainName + " " + (n == 2 ? "20" : "50") + " Day");
maFilter.setPeriod(n == 2 ? 20 : 50);
}
if (onBalanceVolume.getVisible())
volumeGraph.setMessage("On-Balance Volume");
else if (moneyFlowIndex.getVisible())
volumeGraph.setMessage("Money Flow Index");
else
volumeGraph.setMessage(null);
}
}
}
// Handle messages from the panner scrollbar. See the setView()
// method, above.
public void adjustmentValueChanged(AdjustmentEvent e)
{
Scrollbar s = (Scrollbar) e.getAdjustable();
int value = e.getValue();
setGraphViews(value, s.getVisibleAmount());
}
// The LegendPanels inform us that they've been clicked via this
// method. Note: if any of the competing stocks are showing, we
// want to "normalize" the graph, i.e., to display the various
// elements as percent change from the left of the graph. Here's
// how you do it.
public void legendChanged(LegendPanel lp)
{
int count = 0;
for (int i = 0; i < legends.length; i++)
{
if (legends[i].getState()) count++;
stocks[i].setVisible(legends[i].getState());
}
priceGraph.setNormalize(count > 0);
}
}
Copyright (c) 2006 SmartMoney.com
|