ScottPlot.NET
GitHub Repo stars

Financial Charting

Finance charts often display price action next to volume indicators. This page describes how to synchronize two plot controls so they share a horizontal axis and can be used to simultaneously display price and volume.

Key Concepts

  • Add two plots: One for candlesticks, and another for volume
  • Create a candlestick chart and add it to the top plot
  • Create a bar chart and add it to the bottom plot
  • Use the top plot’s AxesChanged event to update the lower plot’s axes as the top plot is panned/zoomed

Note that horizontal axis units are OATime units (one day is 1.0), so one hour is 1.0/34, etc. OHLC objects and bar charts must be placed on the horizontal axis using these units as described by Cookbook: Plotting DateTime Data

Example Code

Note that the full code for this project can be viewed here: FinanceDemo.cs

public partial class FinanceDemo : Form
{
    readonly ScottPlot.Plottable.FinancePlot CandlePlot;
    readonly ScottPlot.Plottable.BarPlot BarPlot;
    readonly Random Rand = new Random(0);

    public FinanceDemo()
    {
        InitializeComponent();

        // setup and style the candlestick
        CandlePlot = new Plottable.FinancePlot() { Candle = true };
        formsPlot1.Plot.Add(CandlePlot);
        formsPlot1.Plot.YLabel("Price");
        formsPlot1.Plot.XAxis.DateTimeFormat(true);
        formsPlot1.Plot.XAxis.Ticks(false);
        formsPlot1.Plot.XAxis.SetSizeLimit(max: 0);
        formsPlot1.AxesChanged += FormsPlot1_AxesChanged; // update plot 2 when plot 1 changes

        double[] xs = { 1, 2, 3 };
        double[] ys = { 1, 2, 3 };
        BarPlot = new Plottable.BarPlot(xs, ys, null, null);

        formsPlot2.Plot.Add(BarPlot);
        formsPlot2.Plot.YLabel("Volume");
        formsPlot2.Plot.XAxis.DateTimeFormat(true);
        formsPlot2.Plot.XAxis2.SetSizeLimit(max: 0);

        for (int i = 0; i < 100; i++)
            AddNewDataPoint();
    }

    private void AddNewDataPoint()
    {
        // generate the next random price (open/high/low/close)
        double lastClose = CandlePlot.OHLCs.Any() ? CandlePlot.OHLCs.Last().Close : 100;
        double open = lastClose + (Rand.NextDouble() - .5) * 10;
        double close = open + (Rand.NextDouble() - .4) * 10;
        double low = Math.Min(open, close) - Rand.NextDouble() * 5;
        double high = Math.Max(open, close) + Rand.NextDouble() * 5;
        double volume = Rand.NextDouble() * 500 + 100;
        TimeSpan span = TimeSpan.FromSeconds(1);
        DateTime date = CandlePlot.OHLCs.Any() 
            ? CandlePlot.OHLCs.Last().DateTime + span 
            : DateTime.Today + TimeSpan.FromHours(9.5);
        OHLC ohlc = new OHLC(open, high, low, close, date, span, volume);
        CandlePlot.Add(ohlc);

        // Automatic set axis limits to show all candles
        formsPlot1.Plot.AxisAuto();

        // Update data for the volume plot
        BarPlot.Replace(
            positions: CandlePlot.OHLCs.Select(x => x.DateTime.ToOADate()).ToArray(),
            values: CandlePlot.OHLCs.Select(x => x.Volume).ToArray());

        // Set the width of each volume bar to that of the time span.
        // Note that units are Microsoft's OATime units which are fractions of a day.
        BarPlot.BarWidth = .9 * span.TotalSeconds / TimeSpan.FromDays(1).TotalSeconds;

        // Automatic set axis limits to show all bars
        formsPlot2.Plot.AxisAuto();
        formsPlot2.Plot.SetAxisLimits(yMin: 0);
    }

    private void FormsPlot1_AxesChanged(object sender, EventArgs e)
    {
        // whenever the price plot axis limits change, set the volume axis limits to match it
        formsPlot2.Plot.MatchAxis(formsPlot1.Plot, horizontal: true, vertical: false);
        formsPlot2.Plot.MatchLayout(formsPlot1.Plot, horizontal: true, vertical: false);
        formsPlot2.Refresh();
    }
}