<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> |