ChartDirector 6.0 (ColdFusion Edition)

Interactive Financial Chart




This example demonstrates a full-featured financial chart in which the user can select the time ranges, technical indicators, and various chart options.

The Interactive Financial Chart is implemented in two parts - "financedemo.cfm" for the containing web page, and "financedemochart.cfm" for the charting page.

The containing web page "financedemo.cfm" is basically just a standard HTML Form. When the "Update Chart" button is pressed, instead of posting to the server, a client side Javascript function is activated. This function combines all Form elements into a query string, appends it to the charting URL "financedemochart.cfm" and uses it to update the <IMG> tag.

The "financedemochart.cfm" draws the finance chart according to the query parameters, which reflects user selections, and outputs the chart to the <IMG> tag.

The "financedemochart.cfm" contains two key parts.

Source Code Listing

[File: cfdemo/financedemo.cfm]
<html>
<cfoutput>
<head>
<title>ChartDirector Financial Chart Demonstration</title>
<style>
.inputtitle {font-size:11px; margin:10px 5px; font-family:verdana}
.input {font-size:11px; font-family:verdana}
</style>
<script language="Javascript">

//Cross browser method to get an object
function getObject(id)
{
    if (document.getElementById)
        //IE 5.x or NS 6.x or above
        return document.getElementById(id);
    else if (document.all)
        //IE 4.x
        return document.all[id];
    else
        //Netscape 4.x
        return document[id];
}

//Update the chart according to user selection
function updateChart()
{
    //
    //we encode the values of all form elements as query parameters
    //
    var elements = getObject("Form1").elements;
    var url = "financedemochart.cfm?";
    for (var i = 0; i < elements.length; ++i)
    {
        var e = elements[i];
        if (e.type == "checkbox")
            url = url + e.id + "=" + (e.checked ? "1" : "0") + "&";
        else
            url = url + e.id + "=" + escape(e.value) + "&";
    }

    //Now we update the URL of the image to update the chart
    getObject("ChartImage").src = url;
}
</script>
</head>
<body style="margin:0px" onLoad="updateChart();">
<table cellspacing="0" cellpadding="0" border="0">
    <tr>
        <td align="right" colspan="2" style="background:##000088">
            <div style="padding-right:3px; padding-bottom:2px; font-family:arial; font-size:10pt; font-weight:bold; font-style:italic">
                <a style="color:##ffff00; text-decoration:none" href="http://www.advsofteng.com">
                    Advanced Software Engineering
                </a>
            </div>
        </td>
    </tr>
    <tr valign="top">
        <td style="width:150px; background:##bbddff">
            <form id="Form1" action="javascript:updateChart()">
            <div class="inputtitle">
                <b>Ticker Symbol</b><br />
                <input id="TickerSymbol" name="TickerSymbol" class="input" style="width:140px;" value="ASE.SYMBOL">
            </div>
            <div class="inputtitle">
                <b>Compare With</b><br />
                <input id="CompareWith" name="CompareWith" class="input" style="width:140px;" value="">
            </div>
            <div class="inputtitle">
                <b>Time Period</b><br />
                <select id="TimeRange" name="TimeRange" class="input" style="width:140px;">
                    <option value="1">1 day</option>
                    <option value="2">2 days</option>
                    <option value="5">5 days</option>
                    <option value="10">10 days</option>
                    <option value="30">1 month</option>
                    <option value="60">2 months</option>
                    <option value="90">3 months</option>
                    <option value="180" selected>6 months</option>
                    <option value="360">1 year</option>
                    <option value="720">2 years</option>
                    <option value="1080">3 years</option>
                    <option value="1440">4 years</option>
                    <option value="1800">5 years</option>
                    <option value="3600">10 years</option>
                </select>
            </div>
            <div class="inputtitle">
                <b>Chart Size</b><br />
                <select id="ChartSize" name="ChartSize" class="input" style="width:140px;">
                    <option value="S">Small</option>
                    <option value="M">Medium</option>
                    <option value="L" selected>Large</option>
                    <option value="H">Huge</option>
                </select><br />
            </div>
            <div class="inputtitle">
                <input type="checkbox" id="Volume" name="Volume" checked><label for="Volume">Show Volume Bars</label><br />
                <input type="checkbox" id="ParabolicSAR" name="ParabolicSAR"><label for="ParabolicSAR">Parabolic SAR</label><br />
                <input type="checkbox" id="LogScale" name="LogScale"><label for="LogScale">Log Scale</label><br />
                <input type="checkbox" id="PercentageScale" name="PercentageScale"><label for="PercentageScale">Percentage Scale</label><br />
            </div>
            <div class="inputtitle">
                <b>Chart Type</b><br />
                <select id="ChartType" name="ChartType" class="input" style="width:140px;">
                    <option value="None">None</option>
                    <option value="CandleStick" selected>CandleStick</option>
                    <option value="Close">Closing Price</option>
                    <option value="Median">Median Price</option>
                    <option value="OHLC">OHLC</option>
                    <option value="TP">Typical Price</option>
                    <option value="WC">Weighted Close</option>
                </select>
            </div>
            <div class="inputtitle">
                <b>Price Band</b><br />
                <select id="Band" name="Band" class="input" style="width:140px;">
                    <option value="None">None</option>
                    <option value="BB" selected>Bollinger Band</option>
                    <option value="DC">Donchian Channel</option>
                    <option value="Envelop">Envelop (SMA 20 +/- 10%)</option>
                </select>
            </div>
            <div class="inputtitle">
                <b>Moving Averages</b><br />
                <nobr><select id="avgType1" name="avgType1" class="input" style="width:105px;">
                    <option value="None">None</option>
                    <option value="SMA" selected>Simple</option>
                    <option value="EMA">Exponential</option>
                    <option value="TMA">Triangular</option>
                    <option value="WMA">Weighted</option>
                </select>
                <input id="movAvg1" name="movAvg1" class="input" style="width:30px;" value="10"></nobr><br />
                <nobr><select id="avgType2" name="avgType2" class="input" style="width:105px;">
                    <option value="None">None</option>
                    <option value="SMA" selected>Simple</option>
                    <option value="EMA">Exponential</option>
                    <option value="TMA">Triangular</option>
                    <option value="WMA">Weighted</option>
                </select>
                <input id="movAvg2" name="movAvg2" class="input" style="width:30px;" value="25"></nobr><br />
            </div>
            <div class="inputtitle">
                <b>Technical Indicators</b><br />
                <select id="Indicator1" name="Indicator1" class="input" style="width:140px;">
                    <option value="None">None</option>
                    <option value="AccDist">Accumulation/Distribution</option>
                    <option value="AroonOsc">Aroon Oscillator</option>
                    <option value="Aroon">Aroon Up/Down</option>
                    <option value="ADX">Avg Directional Index</option>
                    <option value="ATR">Avg True Range</option>
                    <option value="BBW">Bollinger Band Width</option>
                    <option value="CMF">Chaikin Money Flow</option>
                    <option value="COscillator">Chaikin Oscillator</option>
                    <option value="CVolatility">Chaikin Volatility</option>
                    <option value="CLV">Close Location Value</option>
                    <option value="CCI">Commodity Channel Index</option>
                    <option value="DPO">Detrended Price Osc</option>
                    <option value="DCW">Donchian Channel Width</option>
                    <option value="EMV">Ease of Movement</option>
                    <option value="FStoch">Fast Stochastic</option>
                    <option value="MACD">MACD</option>
                    <option value="MDX">Mass Index</option>
                    <option value="Momentum">Momentum</option>
                    <option value="MFI">Money Flow Index</option>
                    <option value="NVI">Neg Volume Index</option>
                    <option value="OBV">On Balance Volume</option>
                    <option value="Performance">Performance</option>
                    <option value="PPO">% Price Oscillator</option>
                    <option value="PVO">% Volume Oscillator</option>
                    <option value="PVI">Pos Volume Index</option>
                    <option value="PVT">Price Volume Trend</option>
                    <option value="ROC">Rate of Change</option>
                    <option value="RSI" selected>RSI</option>
                    <option value="SStoch">Slow Stochastic</option>
                    <option value="StochRSI">StochRSI</option>
                    <option value="TRIX">TRIX</option>
                    <option value="UO">Ultimate Oscillator</option>
                    <option value="Vol">Volume</option>
                    <option value="WilliamR">William's %R</option>
                </select><br />
                <select id="Indicator2" name="Indicator2" class="input" style="width:140px;">
                    <option value="None">None</option>
                    <option value="AccDist">Accumulation/Distribution</option>
                    <option value="AroonOsc">Aroon Oscillator</option>
                    <option value="Aroon">Aroon Up/Down</option>
                    <option value="ADX">Avg Directional Index</option>
                    <option value="ATR">Avg True Range</option>
                    <option value="BBW">Bollinger Band Width</option>
                    <option value="CMF">Chaikin Money Flow</option>
                    <option value="COscillator">Chaikin Oscillator</option>
                    <option value="CVolatility">Chaikin Volatility</option>
                    <option value="CLV">Close Location Value</option>
                    <option value="CCI">Commodity Channel Index</option>
                    <option value="DPO">Detrended Price Osc</option>
                    <option value="DCW">Donchian Channel Width</option>
                    <option value="EMV">Ease of Movement</option>
                    <option value="FStoch">Fast Stochastic</option>
                    <option value="MACD" selected>MACD</option>
                    <option value="MDX">Mass Index</option>
                    <option value="Momentum">Momentum</option>
                    <option value="MFI">Money Flow Index</option>
                    <option value="NVI">Neg Volume Index</option>
                    <option value="OBV">On Balance Volume</option>
                    <option value="Performance">Performance</option>
                    <option value="PPO">% Price Oscillator</option>
                    <option value="PVO">% Volume Oscillator</option>
                    <option value="PVI">Pos Volume Index</option>
                    <option value="PVT">Price Volume Trend</option>
                    <option value="ROC">Rate of Change</option>
                    <option value="RSI">RSI</option>
                    <option value="SStoch">Slow Stochastic</option>
                    <option value="StochRSI">StochRSI</option>
                    <option value="TRIX">TRIX</option>
                    <option value="UO">Ultimate Oscillator</option>
                    <option value="Vol">Volume</option>
                    <option value="WilliamR">William's %R</option>
                </select><br />
                <select id="Indicator3" name="Indicator3" class="input" style="width:140px;">
                    <option value="None" selected>None</option>
                    <option value="AccDist">Accumulation/Distribution</option>
                    <option value="AroonOsc">Aroon Oscillator</option>
                    <option value="Aroon">Aroon Up/Down</option>
                    <option value="ADX">Avg Directional Index</option>
                    <option value="ATR">Avg True Range</option>
                    <option value="BBW">Bollinger Band Width</option>
                    <option value="CMF">Chaikin Money Flow</option>
                    <option value="COscillator">Chaikin Oscillator</option>
                    <option value="CVolatility">Chaikin Volatility</option>
                    <option value="CLV">Close Location Value</option>
                    <option value="CCI">Commodity Channel Index</option>
                    <option value="DPO">Detrended Price Osc</option>
                    <option value="DCW">Donchian Channel Width</option>
                    <option value="EMV">Ease of Movement</option>
                    <option value="FStoch">Fast Stochastic</option>
                    <option value="MACD">MACD</option>
                    <option value="MDX">Mass Index</option>
                    <option value="Momentum">Momentum</option>
                    <option value="MFI">Money Flow Index</option>
                    <option value="NVI">Neg Volume Index</option>
                    <option value="OBV">On Balance Volume</option>
                    <option value="Performance">Performance</option>
                    <option value="PPO">% Price Oscillator</option>
                    <option value="PVO">% Volume Oscillator</option>
                    <option value="PVI">Pos Volume Index</option>
                    <option value="PVT">Price Volume Trend</option>
                    <option value="ROC">Rate of Change</option>
                    <option value="RSI">RSI</option>
                    <option value="SStoch">Slow Stochastic</option>
                    <option value="StochRSI">StochRSI</option>
                    <option value="TRIX">TRIX</option>
                    <option value="UO">Ultimate Oscillator</option>
                    <option value="Vol">Volume</option>
                    <option value="WilliamR">William's %R</option>
                </select><br />
                <select id="Indicator4" name="Indicator4" class="input" style="width:140px;">
                    <option value="None" selected>None</option>
                    <option value="AccDist">Accumulation/Distribution</option>
                    <option value="AroonOsc">Aroon Oscillator</option>
                    <option value="Aroon">Aroon Up/Down</option>
                    <option value="ADX">Avg Directional Index</option>
                    <option value="ATR">Avg True Range</option>
                    <option value="BBW">Bollinger Band Width</option>
                    <option value="CMF">Chaikin Money Flow</option>
                    <option value="COscillator">Chaikin Oscillator</option>
                    <option value="CVolatility">Chaikin Volatility</option>
                    <option value="CLV">Close Location Value</option>
                    <option value="CCI">Commodity Channel Index</option>
                    <option value="DPO">Detrended Price Osc</option>
                    <option value="DCW">Donchian Channel Width</option>
                    <option value="EMV">Ease of Movement</option>
                    <option value="FStoch">Fast Stochastic</option>
                    <option value="MACD">MACD</option>
                    <option value="MDX">Mass Index</option>
                    <option value="Momentum">Momentum</option>
                    <option value="MFI">Money Flow Index</option>
                    <option value="NVI">Neg Volume Index</option>
                    <option value="OBV">On Balance Volume</option>
                    <option value="Performance">Performance</option>
                    <option value="PPO">% Price Oscillator</option>
                    <option value="PVO">% Volume Oscillator</option>
                    <option value="PVI">Pos Volume Index</option>
                    <option value="PVT">Price Volume Trend</option>
                    <option value="ROC">Rate of Change</option>
                    <option value="RSI">RSI</option>
                    <option value="SStoch">Slow Stochastic</option>
                    <option value="StochRSI">StochRSI</option>
                    <option value="TRIX">TRIX</option>
                    <option value="UO">Ultimate Oscillator</option>
                    <option value="Vol">Volume</option>
                    <option value="WilliamR">William's %R</option>
                </select>
            </div>
            <div class="inputtitle" style="text-align:center">
                <input id="Button1" name="Button1" type="submit" class="input" value="Update Chart">
            </div>
            </form>
        </td>
        <td>
            <div style="font-weight:bold;  font-family:arial; font-size:20pt; margin:5px 0px 0px 5px">
                ChartDirector Financial Chart Demonstration
            </div>
            <hr style="border:solid 1px ##000080" />
            <br />
            <img id="ChartImage" align="top" border="0">
        </td>
    </tr>
</table>
</body>
</cfoutput>
</html>

[File: cfdemo/financedemochart.cfm]
<cfscript>

// ChartDirector for ColdFusion API Access Point
cd = CreateObject("java", "ChartDirector.CFChart");

//
// Create a finance chart based on user selections, which are encoded as query parameters. This code
// is designed to work with the financedemo HTML form.
//

// The timeStamps, volume, high, low, open and close data
timeStamps = ArrayNew(1);
volData = ArrayNew(1);
highData = ArrayNew(1);
lowData = ArrayNew(1);
openData = ArrayNew(1);
closeData = ArrayNew(1);

// An extra data series to compare with the close data
compareData = ArrayNew(1);

// The resolution of the data in seconds. 1 day = 86400 seconds.
resolution = 86400;

/// <summary>
/// Get the timeStamps, highData, lowData, openData, closeData and volData.
/// </summary>
/// <param name="ticker">The ticker symbol for the data series.</param>
/// <param name="startDate">The starting date/time for the data series.</param>
/// <param name="endDate">The ending date/time for the data series.</param>
/// <param name="durationInDays">The number of trading days to get.</param>
/// <param name="extraPoints">The extra leading data points needed in order to
/// compute moving averages.</param>
/// <returns>True if successfully obtain the data, otherwise false.</returns>
function getData(ticker, startDate, endDate, durationInDays, extraPoints)
{
    // Declare local variables
    var dataPointsPerDay = 0;

    // This method should return false if the ticker symbol is invalid. In this sample code, as we
    // are using a random number generator for the data, all ticker symbol is allowed, but we still
    // assumed an empty symbol is invalid.
    if (ticker EQ "") {
        return False;
    }

    // In this demo, we can get 15 min, daily, weekly or monthly data depending on the time range.
    resolution = 86400;
    if (durationInDays LTE 10) {
        // 10 days or less, we assume 15 minute data points are available
        resolution = 900;

        // We need to adjust the startDate backwards for the extraPoints. We assume 6.5 hours
        // trading time per day, and 5 trading days per week.
        dataPointsPerDay = 6.5 * 3600 / resolution;
        adjustedStartDate = DateAdd("d", -Ceiling(extraPoints / dataPointsPerDay * 7
            / 5) - 2, CreateDate(Year(startDate), Month(startDate), Day(startDate)));

        // Get the required 15 min data
        get15MinData(ticker, adjustedStartDate, endDate);

    } else if (durationInDays GTE 4.5 * 360) {
        // 4 years or more - use monthly data points.
        resolution = 30 * 86400;

        // Adjust startDate backwards to cater for extraPoints
        adjustedStartDate = DateAdd("m", -extraPoints, startDate);

        // Get the required monthly data
        getMonthlyData(ticker, adjustedStartDate, endDate);

    } else if (durationInDays GTE 1.5 * 360) {
        // 1 year or more - use weekly points.
        resolution = 7 * 86400;

        // Adjust startDate backwards to cater for extraPoints
        adjustedStartDate = DateAdd("d", -extraPoints * 7 - 6, startDate);

        // Get the required weekly data
        getWeeklyData(ticker, adjustedStartDate, endDate);

    } else {
        // Default - use daily points
        resolution = 86400;

        // Adjust startDate backwards to cater for extraPoints. We multiply the days by 7/5 as we
        // assume 1 week has 5 trading days.
        adjustedStartDate = DateAdd("d", -Int((extraPoints * 7 + 4) / 5) - 2,
            CreateDate(Year(startDate), Month(startDate), Day(startDate)));

        // Get the required daily data
        getDailyData(ticker, adjustedStartDate, endDate);
    }

    return True;
}

/// <summary>
/// Get 15 minutes data series for timeStamps, highData, lowData, openData, closeData
/// and volData.
/// </summary>
/// <param name="ticker">The ticker symbol for the data series.</param>
/// <param name="startDate">The starting date/time for the data series.</param>
/// <param name="endDate">The ending date/time for the data series.</param>
function get15MinData(ticker, startDate, endDate)
{
    //
    // In this demo, we use a random number generator to generate the data. In practice, you may get
    // the data from a database or by other means. If you do not have 15 minute data, you may modify
    // the "drawChart" method below to not using 15 minute data.
    //
    generateRandomData(ticker, startDate, endDate, 900);
}

/// <summary>
/// Get daily data series for timeStamps, highData, lowData, openData, closeData
/// and volData.
/// </summary>
/// <param name="ticker">The ticker symbol for the data series.</param>
/// <param name="startDate">The starting date/time for the data series.</param>
/// <param name="endDate">The ending date/time for the data series.</param>
function getDailyData(ticker, startDate, endDate)
{
    //
    // In this demo, we use a random number generator to generate the data. In practice, you may get
    // the data from a database or by other means.
    //
    generateRandomData(ticker, startDate, endDate, 86400);
}

/// <summary>
/// Get weekly data series for timeStamps, highData, lowData, openData, closeData
/// and volData.
/// </summary>
/// <param name="ticker">The ticker symbol for the data series.</param>
/// <param name="startDate">The starting date/time for the data series.</param>
/// <param name="endDate">The ending date/time for the data series.</param>
function getWeeklyData(ticker, startDate, endDate)
{
    //
    // If you do not have weekly data, you may call "getDailyData(startDate, endDate)" to get daily
    // data, then call "convertDailyToWeeklyData()" to convert to weekly data.
    //
    generateRandomData(ticker, startDate, endDate, 86400 * 7);
}

/// <summary>
/// Get monthly data series for timeStamps, highData, lowData, openData, closeData
/// and volData.
/// </summary>
/// <param name="ticker">The ticker symbol for the data series.</param>
/// <param name="startDate">The starting date/time for the data series.</param>
/// <param name="endDate">The ending date/time for the data series.</param>
function getMonthlyData(ticker, startDate, endDate)
{
    //
    // If you do not have weekly data, you may call "getDailyData(startDate, endDate)" to get daily
    // data, then call "convertDailyToMonthlyData()" to convert to monthly data.
    //
    generateRandomData(ticker, startDate, endDate, 86400 * 30);
}

/// <summary>
/// A random number generator designed to generate realistic financial data.
/// </summary>
/// <param name="ticker">The ticker symbol for the data series.</param>
/// <param name="startDate">The starting date/time for the data series.</param>
/// <param name="endDate">The ending date/time for the data series.</param>
/// <param name="resolution">The period of the data series.</param>
function generateRandomData(ticker, startDate, endDate, resolution)
{
    // Declare local variables
    var db = 0;

    db = cd.FinanceSimulator(ticker, startDate, endDate, resolution);
    timeStamps = db.getTimeStamps();
    highData = db.getHighData();
    lowData = db.getLowData();
    openData = db.getOpenData();
    closeData = db.getCloseData();
    volData = db.getVolData();
}

/// <summary>
/// A utility to convert daily to weekly data.
/// </summary>
function convertDailyToWeeklyData()
{
    aggregateData(cd.ArrayMath(timeStamps).selectStartOfWeek());
}

/// <summary>
/// A utility to convert daily to monthly data.
/// </summary>
function convertDailyToMonthlyData()
{
    aggregateData(cd.ArrayMath(timeStamps).selectStartOfMonth());
}

/// <summary>
/// An internal method used to aggregate daily data.
/// </summary>
function aggregateData(aggregator)
{
    timeStamps = aggregator.aggregate(timeStamps, cd.AggregateFirst);
    highData = aggregator.aggregate(highData, cd.AggregateMax);
    lowData = aggregator.aggregate(lowData, cd.AggregateMin);
    openData = aggregator.aggregate(openData, cd.AggregateFirst);
    closeData = aggregator.aggregate(closeData, cd.AggregateLast);
    volData = aggregator.aggregate(volData, cd.AggregateSum);
}

/// <summary>
/// Create a financial chart according to user selections. The user selections are
/// encoded in the query parameters.
/// </summary>
function drawChart()
{
    // Declare local variables
    var endDate = 0;
    var durationInDays = 0;
    var startDate = 0;
    var avgPeriod1 = 0;
    var avgPeriod2 = 0;
    var extraPoints = 0;
    var compareKey = 0;
    var tickerKey = 0;
    var i = 0;
    var lastTime = 0;
    var width = 0;
    var mainHeight = 0;
    var indicatorHeight = 0;
    var size = 0;
    var m = 0;
    var resolutionText = 0;
    var mainType = 0;
    var bandType = 0;

    // In this demo, we just assume we plot up to the latest time. So end date is now.
    endDate = Now();

    // If the trading day has not yet started (before 9:30am), or if the end date is on on Sat or
    // Sun, we set the end date to 4:00pm of the last trading day
    while ((Hour(endDate) * 60 + Minute(endDate) LT 9 * 60 + 30) Or
        DayOfWeek(endDate) EQ 1 Or DayOfWeek(endDate) EQ 7) {
        endDate = DateAdd("d", -1, CreateDateTime(Year(endDate), Month(endDate),
            Day(endDate), 16, 0, 0));
    }

    // The duration selected by the user
    durationInDays = Int(URL.TimeRange);

    // Compute the start date by subtracting the duration from the end date.
    startDate = endDate;
    if (durationInDays GTE 30) {
        // More or equal to 30 days - so we use months as the unit
        startDate = DateAdd("m", - durationInDays \ 30,
            CreateDate(Year(endDate), Month(endDate), 1));
    } else {
        // Less than 30 days - use day as the unit. The starting point of the axis is always at the
        // start of the day (9:30am). Note that we use trading days, so we skip Sat and Sun in
        // counting the days.
        startDate = CreateDateTime(Year(endDate), Month(endDate), Day(endDate),
            9, 30, 0);
        for (i = 1; i LT durationInDays; i = i + 1) {
            if (DayOfWeek(startDate) EQ 2) {
                startDate = DateAdd("d", -3, startDate);
            } else {
                startDate = DateAdd("d", -1, startDate);
            }
        }
    }

    // The moving average periods selected by the user.
    avgPeriod1 = 0;
    if (IsNumeric(URL.movAvg1)) {
        avgPeriod1 = Int(URL.movAvg1);;
    } else {
        avgPeriod1 = 0;;
    }
    avgPeriod2 = 0;
    if (IsNumeric(URL.movAvg2)) {
        avgPeriod2 = Int(URL.movAvg2);;
    } else {
        avgPeriod2 = 0;;
    }

    if (avgPeriod1 LT 0) {
        avgPeriod1 = 0;
    } else if (avgPeriod1 GT 300) {
        avgPeriod1 = 300;
    }

    if (avgPeriod2 LT 0) {
        avgPeriod2 = 0;
    } else if (avgPeriod2 GT 300) {
        avgPeriod2 = 300;
    }

    // We need extra leading data points in order to compute moving averages.
    extraPoints = 20;
    if (avgPeriod1 GT extraPoints) {
        extraPoints = avgPeriod1;
    }
    if (avgPeriod2 GT extraPoints) {
        extraPoints = avgPeriod2;
    }

    // Get the data series to compare with, if any.
    compareKey = Trim(URL.CompareWith);
    compareData = ArrayNew(1);
    if (getData(compareKey, startDate, endDate, durationInDays, extraPoints)) {
          compareData = closeData;
    }

    // The data series we want to get.
    tickerKey = Trim(URL.TickerSymbol);
    if (Not getData(tickerKey, startDate, endDate, durationInDays, extraPoints)) {
        return errMsg("Please enter a valid ticker symbol");
    }

    // We now confirm the actual number of extra points (data points that are before the start date)
    // as inferred using actual data from the database.
    extraPoints = ArrayLen(timeStamps);
    for (i = 0; i LT ArrayLen(timeStamps); i = i + 1) {
        if (timeStamps[i + 1] GTE startDate) {
            extraPoints = i;
            break;
        }
    }

    // Check if there is any valid data
    if (extraPoints GTE ArrayLen(timeStamps)) {
        // No data - just display the no data message.
        return errMsg("No data available for the specified time period");
    }

    // In some finance chart presentation style, even if the data for the latest day is not fully
    // available, the axis for the entire day will still be drawn, where no data will appear near
    // the end of the axis.
    if (resolution LT 86400) {
        // Add extra points to the axis until it reaches the end of the day. The end of day is
        // assumed to be 16:00 (it depends on the stock exchange).
        lastTime = timeStamps[ArrayLen(timeStamps)];
        extraTrailingPoints = Int((16 * 3600.0 - Hour(lastTime) * 3600 -
            Minute(lastTime) * 60 - Second(lastTime)) / resolution);
        for (i = 0; i LT extraTrailingPoints; i = i + 1) {
            ArrayAppend(timeStamps, DateAdd("s", resolution * (i + 1), lastTime));
        }
    }

    //
    // At this stage, all data are available. We can draw the chart as according to user input.
    //

    //
    // Determine the chart size. In this demo, user can select 4 different chart sizes. Default is
    // the large chart size.
    //
    width = 780;
    mainHeight = 255;
    indicatorHeight = 80;

    size = URL.ChartSize;
    if (size EQ "S") {
        // Small chart size
        width = 450;
        mainHeight = 160;
        indicatorHeight = 60;
    } else if (size EQ "M") {
        // Medium chart size
        width = 620;
        mainHeight = 215;
        indicatorHeight = 70;
    } else if (size EQ "H") {
        // Huge chart size
        width = 1000;
        mainHeight = 320;
        indicatorHeight = 90;
    }

    // Create the chart object using the selected size
    m = cd.FinanceChart(width);

    // Set the data into the chart object
    m.setData(timeStamps, highData, lowData, openData, closeData, volData, extraPoints);

    //
    // We configure the title of the chart. In this demo chart design, we put the company name as
    // the top line of the title with left alignment.
    //
    m.addPlotAreaTitle(cd.TopLeft, tickerKey);

    // We displays the current date as well as the data resolution on the next line.
    resolutionText = "";
    if (resolution EQ 30 * 86400) {
        resolutionText = "Monthly";
    } else if (resolution EQ 7 * 86400) {
        resolutionText = "Weekly";
    } else if (resolution EQ 86400) {
        resolutionText = "Daily";
    } else if (resolution EQ 900) {
        resolutionText = "15-min";
    }

    m.addPlotAreaTitle(cd.BottomLeft, "<*font=Arial,size=8*>" & m.formatValue(Now(), "mmm dd, yyyy")
         & " - " & resolutionText & " chart");

    // A copyright message at the bottom left corner the title area
    m.addPlotAreaTitle(cd.BottomRight, "<*font=Arial,size=8*>(c) Advanced Software Engineering");

    //
    // Add the first techical indicator according. In this demo, we draw the first indicator on top
    // of the main chart.
    //
    addIndicator(m, URL.Indicator1, indicatorHeight);

    //
    // Add the main chart
    //
    m.addMainChart(mainHeight);

    //
    // Set log or linear scale according to user preference
    //
    if (URL.LogScale EQ "1") {
        m.setLogScale(True);
    }

    //
    // Set axis labels to show data values or percentage change to user preference
    //
    if (URL.PercentageScale EQ "1") {
        m.setPercentageAxis();
    }

    //
    // Draw any price line the user has selected
    //
    mainType = URL.ChartType;
    if (mainType EQ "Close") {
        m.addCloseLine("0x000040");
    } else if (mainType EQ "TP") {
        m.addTypicalPrice("0x000040");
    } else if (mainType EQ "WC") {
        m.addWeightedClose("0x000040");
    } else if (mainType EQ "Median") {
        m.addMedianPrice("0x000040");
    }

    //
    // Add comparison line if there is data for comparison
    //
    if (ArrayLen(compareData) GT 0) {
        if (ArrayLen(compareData) GT extraPoints) {
            m.addComparison(compareData, "0x0000ff", compareKey);
        }
    }

    //
    // Add moving average lines.
    //
    addMovingAvg(m, URL.avgType1, avgPeriod1, "0x663300");
    addMovingAvg(m, URL.avgType2, avgPeriod2, "0x9900ff");

    //
    // Draw candlesticks or OHLC symbols if the user has selected them.
    //
    if (mainType EQ "CandleStick") {
        m.addCandleStick("0x33ff33", "0xff3333");
    } else if (mainType EQ "OHLC") {
        m.addHLOC("0x008800", "0xcc0000");
    }

    //
    // Add parabolic SAR if necessary
    //
    if (URL.ParabolicSAR EQ "1") {
        m.addParabolicSAR(0.02, 0.02, 0.2, cd.DiamondShape, 5, "0x008800", "0x000000");
    }

    //
    // Add price band/channel/envelop to the chart according to user selection
    //
    bandType = URL.Band;
    if (bandType EQ "BB") {
        m.addBollingerBand(20, 2, "0x9999ff", "0xc06666ff");
    } else if (bandType EQ "DC") {
        m.addDonchianChannel(20, "0x9999ff", "0xc06666ff");
    } else if (bandType EQ "Envelop") {
        m.addEnvelop(20, 0.1, "0x9999ff", "0xc06666ff");
    }

    //
    // Add volume bars to the main chart if necessary
    //
    if (URL.Volume EQ "1") {
        m.addVolBars(indicatorHeight, "0x99ff99", "0xff9999", "0xc0c0c0");
    }

    //
    // Add additional indicators as according to user selection.
    //
    addIndicator(m, URL.Indicator2, indicatorHeight);
    addIndicator(m, URL.Indicator3, indicatorHeight);
    addIndicator(m, URL.Indicator4, indicatorHeight);

    return m;
}

/// <summary>
/// Add a moving average line to the FinanceChart object.
/// </summary>
/// <param name="m">The FinanceChart object to add the line to.</param>
/// <param name="avgType">The moving average type (SMA/EMA/TMA/WMA).</param>
/// <param name="avgPeriod">The moving average period.</param>
/// <param name="color">The color of the line.</param>
/// <returns>The LineLayer object representing line layer created.</returns>
function addMovingAvg(m, avgType, avgPeriod, color)
{
    if (avgPeriod GT 1) {
        if (avgType EQ "SMA") {
            return m.addSimpleMovingAvg(avgPeriod, color);
        } else if (avgType EQ "EMA") {
            return m.addExpMovingAvg(avgPeriod, color);
        } else if (avgType EQ "TMA") {
            return m.addTriMovingAvg(avgPeriod, color);
        } else if (avgType EQ "WMA") {
            return m.addWeightedMovingAvg(avgPeriod, color);
        }
    }
    return Javacast("null", "");
}

/// <summary>
/// Add an indicator chart to the FinanceChart object. In this demo example, the
/// indicator parameters (such as the period used to compute RSI, colors of the lines,
/// etc.) are hard coded to commonly used values. You are welcome to design a more
/// complex user interface to allow users to set the parameters.
/// </summary>
/// <param name="m">The FinanceChart object to add the line to.</param>
/// <param name="indicator">The selected indicator.</param>
/// <param name="height">Height of the chart in pixels</param>
/// <returns>The XYChart object representing indicator chart.</returns>
function addIndicator(m, indicator, height)
{
    if (indicator EQ "RSI") {
        return m.addRSI(height, 14, "0x800080", 20, "0xff6666", "0x6666ff");
    } else if (indicator EQ "StochRSI") {
        return m.addStochRSI(height, 14, "0x800080", 30, "0xff6666", "0x6666ff");
    } else if (indicator EQ "MACD") {
        return m.addMACD(height, 26, 12, 9, "0x0000ff", "0xff00ff", "0x008000");
    } else if (indicator EQ "FStoch") {
        return m.addFastStochastic(height, 14, 3, "0x006060", "0x606000");
    } else if (indicator EQ "SStoch") {
        return m.addSlowStochastic(height, 14, 3, "0x006060", "0x606000");
    } else if (indicator EQ "ATR") {
        return m.addATR(height, 14, "0x808080", "0x0000ff");
    } else if (indicator EQ "ADX") {
        return m.addADX(height, 14, "0x008000", "0x800000", "0x000080");
    } else if (indicator EQ "DCW") {
        return m.addDonchianWidth(height, 20, "0x0000ff");
    } else if (indicator EQ "BBW") {
        return m.addBollingerWidth(height, 20, 2, "0x0000ff");
    } else if (indicator EQ "DPO") {
        return m.addDPO(height, 20, "0x0000ff");
    } else if (indicator EQ "PVT") {
        return m.addPVT(height, "0x0000ff");
    } else if (indicator EQ "Momentum") {
        return m.addMomentum(height, 12, "0x0000ff");
    } else if (indicator EQ "Performance") {
        return m.addPerformance(height, "0x0000ff");
    } else if (indicator EQ "ROC") {
        return m.addROC(height, 12, "0x0000ff");
    } else if (indicator EQ "OBV") {
        return m.addOBV(height, "0x0000ff");
    } else if (indicator EQ "AccDist") {
        return m.addAccDist(height, "0x0000ff");
    } else if (indicator EQ "CLV") {
        return m.addCLV(height, "0x0000ff");
    } else if (indicator EQ "WilliamR") {
        return m.addWilliamR(height, 14, "0x800080", 30, "0xff6666", "0x6666ff");
    } else if (indicator EQ "Aroon") {
        return m.addAroon(height, 14, "0x339933", "0x333399");
    } else if (indicator EQ "AroonOsc") {
        return m.addAroonOsc(height, 14, "0x0000ff");
    } else if (indicator EQ "CCI") {
        return m.addCCI(height, 20, "0x800080", 100, "0xff6666", "0x6666ff");
    } else if (indicator EQ "EMV") {
        return m.addEaseOfMovement(height, 9, "0x006060", "0x606000");
    } else if (indicator EQ "MDX") {
        return m.addMassIndex(height, "0x800080", "0xff6666", "0x6666ff");
    } else if (indicator EQ "CVolatility") {
        return m.addChaikinVolatility(height, 10, 10, "0x0000ff");
    } else if (indicator EQ "COscillator") {
        return m.addChaikinOscillator(height, "0x0000ff");
    } else if (indicator EQ "CMF") {
        return m.addChaikinMoneyFlow(height, 21, "0x008000");
    } else if (indicator EQ "NVI") {
        return m.addNVI(height, 255, "0x0000ff", "0x883333");
    } else if (indicator EQ "PVI") {
        return m.addPVI(height, 255, "0x0000ff", "0x883333");
    } else if (indicator EQ "MFI") {
        return m.addMFI(height, 14, "0x800080", 30, "0xff6666", "0x6666ff");
    } else if (indicator EQ "PVO") {
        return m.addPVO(height, 26, 12, 9, "0x0000ff", "0xff00ff", "0x008000");
    } else if (indicator EQ "PPO") {
        return m.addPPO(height, 26, 12, 9, "0x0000ff", "0xff00ff", "0x008000");
    } else if (indicator EQ "UO") {
        return m.addUltimateOscillator(height, 7, 14, 28, "0x800080", 20, "0xff6666", "0x6666ff");
    } else if (indicator EQ "Vol") {
        return m.addVolIndicator(height, "0x99ff99", "0xff9999", "0xc0c0c0");
    } else if (indicator EQ "TRIX") {
        return m.addTRIX(height, 12, "0x0000ff");
    }
    return Javacast("null", "");
}

/// <summary>
/// Creates a dummy chart to show an error message.
/// </summary>
/// <param name="msg">The error message.
/// <returns>The BaseChart object containing the error message.</returns>
function errMsg(msg)
{
    // Declare local variables
    var m = 0;

    m = cd.MultiChart(400, 200);
    m.addTitle2(cd.Center, msg, "Arial", 10).setMaxWidth(m.getWidth());
    return m;
}

// create the finance chart
c = drawChart();

// Output the chart
chart1URL = c.makeSession(GetPageContext(), "chart1");

// Stream chart directly to browser
GetPageContext().forward("getchart.cfm?" & chart1URL);

</cfscript>