#!/usr/bin/perl
# Include current script directory in the module path (needed on Microsoft IIS).
# This allows this script to work by copying ChartDirector to the same directory
# as the script (as an alternative to installation in Perl module directory)
use File::Basename;
use lib dirname($0) =~ /(.*)/;
use perlchartdir;
#/////////////////////////////////////////////////////////////////////////////////////////////////
# Copyright 2008 Advanced Software Engineering Limited
#
# ChartDirector FinanceChart class library
# - Requires ChartDirector Ver 5.0 or above
#
# You may use and modify the code in this file in your application, provided the code and
# its modifications are used only in conjunction with ChartDirector. Usage of this software
# is subjected to the terms and condition of the ChartDirector license.
#/////////////////////////////////////////////////////////////////////////////////////////////////
#/
#/ Represents a Financial Chart
#/
package FinanceChart;
@ISA = ("MultiChart");
sub init
{
my $self = shift;
$self->{'m_totalWidth'} = 0;
$self->{'m_totalHeight'} = 0;
$self->{'m_antiAlias'} = 1;
$self->{'m_logScale'} = 0;
$self->{'m_axisOnRight'} = 1;
$self->{'m_leftMargin'} = 40;
$self->{'m_rightMargin'} = 40;
$self->{'m_topMargin'} = 30;
$self->{'m_bottomMargin'} = 35;
$self->{'m_plotAreaBgColor'} = 0xffffff;
$self->{'m_plotAreaBorder'} = 0x888888;
$self->{'m_plotAreaGap'} = 2;
$self->{'m_majorHGridColor'} = 0xdddddd;
$self->{'m_minorHGridColor'} = 0xdddddd;
$self->{'m_majorVGridColor'} = 0xdddddd;
$self->{'m_minorVGridColor'} = 0xdddddd;
$self->{'m_legendFont'} = "normal";
$self->{'m_legendFontSize'} = 8;
$self->{'m_legendFontColor'} = $perlchartdir::TextColor;
$self->{'m_legendBgColor'} = 0x80cccccc;
$self->{'m_yAxisFont'} = "normal";
$self->{'m_yAxisFontSize'} = 8;
$self->{'m_yAxisFontColor'} = $perlchartdir::TextColor;
$self->{'m_yAxisMargin'} = 14;
$self->{'m_xAxisFont'} = "normal";
$self->{'m_xAxisFontSize'} = 8;
$self->{'m_xAxisFontColor'} = $perlchartdir::TextColor;
$self->{'m_xAxisFontAngle'} = 0;
$self->{'m_timeStamps'} = undef;
$self->{'m_highData'} = undef;
$self->{'m_lowData'} = undef;
$self->{'m_openData'} = undef;
$self->{'m_closeData'} = undef;
$self->{'m_volData'} = undef;
$self->{'m_volUnit'} = "";
$self->{'m_extraPoints'} = 0;
$self->{'m_yearFormat'} = "{value|yyyy}";
$self->{'m_firstMonthFormat'} = "<*font=bold*>{value|mmm yy}";
$self->{'m_otherMonthFormat'} = "{value|mmm}";
$self->{'m_firstDayFormat'} = "<*font=bold*>{value|d mmm}";
$self->{'m_otherDayFormat'} = "{value|d}";
$self->{'m_firstHourFormat'} = "<*font=bold*>{value|d mmm\nh:nna}";
$self->{'m_otherHourFormat'} = "{value|h:nna}";
$self->{'m_timeLabelSpacing'} = 50;
$self->{'m_generalFormat'} = "P3";
$self->{'m_toolTipMonthFormat'} = "[{xLabel|mmm yyyy}]";
$self->{'m_toolTipDayFormat'} = "[{xLabel|mmm d, yyyy}]";
$self->{'m_toolTipHourFormat'} = "[{xLabel|mmm d, yyyy hh:nn:ss}]";
$self->{'m_mainChart'} = undef;
$self->{'m_currentChart'} = undef;
}
#/
#/ Create a FinanceChart with a given width. The height will be automatically determined
#/ as the chart is built.
#/
#/ Width of the chart in pixels
sub new
{
my ($class, $width) = @_;
my $self = $class->SUPER::new($width, 1);
bless($self, $class);
$self->FinanceChart::init();
$self->{'m_totalWidth'} = $width;
return $self;
}
#/
#/ Enable/Disable anti-alias. Enabling anti-alias makes the line smoother. Disabling
#/ anti-alias make the chart file size smaller, and so can be downloaded faster
#/ through the Internet. The default is to enable anti-alias.
#/
#/ True to enable anti-alias. False to disable anti-alias.
sub enableAntiAlias
{
my ($self, $antiAlias) = @_;
$self->{'m_antiAlias'} = $antiAlias;
}
#/
#/ Set the margins around the plot area.
#/
#/ The distance between the plot area and the chart left edge.
#/ The distance between the plot area and the chart top edge.
#/ The distance between the plot area and the chart right edge.
#/ The distance between the plot area and the chart bottom edge.
sub setMargins
{
my ($self, $leftMargin, $topMargin, $rightMargin, $bottomMargin) = @_;
$self->{'m_leftMargin'} = $leftMargin;
$self->{'m_rightMargin'} = $rightMargin;
$self->{'m_topMargin'} = $topMargin;
$self->{'m_bottomMargin'} = $bottomMargin;
}
#/
#/ Add a text title above the plot area. You may add multiple title above the plot area by
#/ calling this method multiple times.
#/
#/ The alignment with respect to the region that is on top of the
#/ plot area.
#/ The text to add.
#/ The TextBox object representing the text box above the plot area.
sub addPlotAreaTitle
{
my ($self, $alignment, $text) = @_;
my $ret = $self->addText($self->{'m_leftMargin'}, 0, $text, "bold", 10,
$perlchartdir::TextColor, $alignment);
$ret->setSize($self->{'m_totalWidth'} - $self->{'m_leftMargin'} - $self->{'m_rightMargin'} + 1,
$self->{'m_topMargin'} - 1);
$ret->setMargin(0);
return $ret;
}
#/
#/ Set the plot area style. The default is to use pale yellow 0xfffff0 as the background,
#/ and light grey 0xdddddd as the grid lines.
#/
#/ The plot area background color.
#/ Major horizontal grid color.
#/ Major vertical grid color.
#/ Minor horizontal grid color. In current version, minor
#/ horizontal grid is not used.
#/ Minor vertical grid color.
sub setPlotAreaStyle
{
my ($self, $bgColor, $majorHGridColor, $majorVGridColor, $minorHGridColor, $minorVGridColor)
= @_;
$self->{'m_plotAreaBgColor'} = $bgColor;
$self->{'m_majorHGridColor'} = $majorHGridColor;
$self->{'m_majorVGridColor'} = $majorVGridColor;
$self->{'m_minorHGridColor'} = $minorHGridColor;
$self->{'m_minorVGridColor'} = $minorVGridColor;
}
#/
#/ Set the plot area border style. The default is grey color (888888), with a gap
#/ of 2 pixels between charts.
#/
#/ The color of the border.
#/ The gap between two charts.
sub setPlotAreaBorder
{
my ($self, $borderColor, $borderGap) = @_;
$self->{'m_plotAreaBorder'} = $borderColor;
$self->{'m_plotAreaGap'} = $borderGap;
}
#/
#/ Set legend style. The default is Arial 8 pt black color, with light grey background.
#/
#/ The font of the legend text.
#/ The font size of the legend text in points.
#/ The color of the legend text.
#/ The background color of the legend box.
sub setLegendStyle
{
my ($self, $font, $fontSize, $fontColor, $bgColor) = @_;
$self->{'m_legendFont'} = $font;
$self->{'m_legendFontSize'} = $fontSize;
$self->{'m_legendFontColor'} = $fontColor;
$self->{'m_legendBgColor'} = $bgColor;
}
#/
#/ Set x-axis label style. The default is Arial 8 pt black color no rotation.
#/
#/ The font of the axis labels.
#/ The font size of the axis labels in points.
#/ The color of the axis labels.
#/ The rotation of the axis labels.
sub setXAxisStyle
{
my ($self, $font, $fontSize, $fontColor, $fontAngle) = @_;
$self->{'m_xAxisFont'} = $font;
$self->{'m_xAxisFontSize'} = $fontSize;
$self->{'m_xAxisFontColor'} = $fontColor;
$self->{'m_xAxisFontAngle'} = $fontAngle;
}
#/
#/ Set y-axis label style. The default is Arial 8 pt black color, with 13 pixels margin.
#/
#/ The font of the axis labels.
#/ The font size of the axis labels in points.
#/ The color of the axis labels.
#/ The margin at the top of the y-axis in pixels (to leave
#/ space for the legend box).
sub setYAxisStyle
{
my ($self, $font, $fontSize, $fontColor, $axisMargin) = @_;
$self->{'m_yAxisFont'} = $font;
$self->{'m_yAxisFontSize'} = $fontSize;
$self->{'m_yAxisFontColor'} = $fontColor;
$self->{'m_yAxisMargin'} = $axisMargin;
}
#/
#/ Set whether the main y-axis is on right of left side of the plot area. The default is
#/ on right.
#/
#/ True if the y-axis is on right. False if the y-axis is on left.
sub setAxisOnRight
{
my ($self, $b) = @_;
$self->{'m_axisOnRight'} = $b;
}
#/
#/ Determines if log scale should be used for the main chart. The default is linear scale.
#/
#/ True for using log scale. False for using linear scale.
sub setLogScale
{
my ($self, $b) = @_;
$self->{'m_logScale'} = $b;
if (defined($self->{'m_mainChart'})) {
if ($self->{'m_logScale'}) {
$self->{'m_mainChart'}->yAxis()->setLogScale();
} else {
$self->{'m_mainChart'}->yAxis()->setLinearScale();
}
}
}
#/
#/ Set the date/time formats to use for the x-axis labels under various cases.
#/
#/ The format for displaying labels on an axis with yearly ticks. The
#/ default is "yyyy".
#/ The format for displaying labels on an axis with monthly ticks.
#/ This parameter applies to the first available month of a year (usually January) only, so it can
#/ be formatted differently from the other labels.
#/ The format for displaying labels on an axis with monthly ticks.
#/ This parameter applies to months other than the first available month of a year.
#/ The format for displaying labels on an axis with daily ticks.
#/ This parameter applies to the first available day of a month only, so it can be formatted
#/ differently from the other labels.
#/ The format for displaying labels on an axis with daily ticks.
#/ This parameter applies to days other than the first available day of a month.
#/ The format for displaying labels on an axis with hourly
#/ resolution. This parameter applies to the first tick of a day only, so it can be formatted
#/ differently from the other labels.
#/ The format for displaying labels on an axis with hourly.
#/ resolution. This parameter applies to ticks at hourly boundaries, except the first tick
#/ of a day.
sub setDateLabelFormat
{
my ($self, $yearFormat, $firstMonthFormat, $otherMonthFormat, $firstDayFormat, $otherDayFormat,
$firstHourFormat, $otherHourFormat) = @_;
if (defined($yearFormat)) {
$self->{'m_yearFormat'} = $yearFormat;
}
if (defined($firstMonthFormat)) {
$self->{'m_firstMonthFormat'} = $firstMonthFormat;
}
if (defined($otherMonthFormat)) {
$self->{'m_otherMonthFormat'} = $otherMonthFormat;
}
if (defined($firstDayFormat)) {
$self->{'m_firstDayFormat'} = $firstDayFormat;
}
if (defined($otherDayFormat)) {
$self->{'m_otherDayFormat'} = $otherDayFormat;
}
if (defined($firstHourFormat)) {
$self->{'m_firstHourFormat'} = $firstHourFormat;
}
if (defined($otherHourFormat)) {
$self->{'m_otherHourFormat'} = $otherHourFormat;
}
}
#/
#/ Set the minimum label spacing between two labels on the time axis
#/
#/ The minimum label spacing in pixels.
sub setDateLabelSpacing
{
my ($self, $labelSpacing) = @_;
if ($labelSpacing > 0) {
$self->{'m_timeLabelSpacing'} = $labelSpacing;
} else {
$self->{'m_timeLabelSpacing'} = 0;
}
}
#/
#/ Set the tool tip formats for display date/time
#/
#/ The tool tip format to use if the data point spacing is one
#/ or more months (more than 30 days).
#/ The tool tip format to use if the data point spacing is 1 day
#/ to less than 30 days.
#/ The tool tip format to use if the data point spacing is less
#/ than 1 day.
sub setToolTipDateFormat
{
my ($self, $monthFormat, $dayFormat, $hourFormat) = @_;
if (defined($monthFormat)) {
$self->{'m_toolTipMonthFormat'} = $monthFormat;
}
if (defined($dayFormat)) {
$self->{'m_toolTipDayFormat'} = $dayFormat;
}
if (defined($hourFormat)) {
$self->{'m_toolTipHourFormat'} = $hourFormat;
}
}
#/
#/ Get the tool tip format for display date/time
#/
#/ The tool tip format string.
sub getToolTipDateFormat
{
my ($self) = @_;
if (!defined($self->{'m_timeStamps'})) {
return $self->{'m_toolTipHourFormat'};
}
if (scalar(@{$self->{'m_timeStamps'}}) <= $self->{'m_extraPoints'}) {
return $self->{'m_toolTipHourFormat'};
}
my $resolution = ($self->{'m_timeStamps'}->[scalar(@{$self->{'m_timeStamps'}}) - 1] -
$self->{'m_timeStamps'}->[0]) / scalar(@{$self->{'m_timeStamps'}});
if ($resolution >= 30 * 86400) {
return $self->{'m_toolTipMonthFormat'};
} elsif ($resolution >= 86400) {
return $self->{'m_toolTipDayFormat'};
} else {
return $self->{'m_toolTipHourFormat'};
}
}
#/
#/ Set the number format for use in displaying values in legend keys and tool tips.
#/
#/ The default number format.
sub setNumberLabelFormat
{
my ($self, $formatString) = @_;
if (defined($formatString)) {
$self->{'m_generalFormat'} = $formatString;
}
}
#/
#/ A utility function to compute triangular moving averages
#/
#/ An array of numbers as input.
#/ The moving average period.
#/ An array representing the triangular moving average of the input array.
sub computeTriMovingAvg
{
my ($self, $data, $period) = @_;
my $p = $period / 2 + 1;
return new ArrayMath($data)->movAvg($p)->movAvg($p)->result();
}
#/
#/ A utility function to compute weighted moving averages
#/
#/ An array of numbers as input.
#/ The moving average period.
#/ An array representing the weighted moving average of the input array.
sub computeWeightedMovingAvg
{
my ($self, $data, $period) = @_;
my $acc = new ArrayMath($data);
for(my $i = 2; $i < $period + 1; ++$i) {
$acc->add(new ArrayMath($data)->movAvg($i)->mul($i)->result());
}
return $acc->div((1 + $period) * $period / 2)->result();
}
#/
#/ A utility function to obtain the first visible closing price.
#/
#/ The first closing price.
#/ are cd.NoValue.
sub firstCloseValue
{
my ($self) = @_;
for(my $i = $self->{'m_extraPoints'}; $i < scalar(@{$self->{'m_closeData'}}); ++$i) {
if (($self->{'m_closeData'}->[$i] != $perlchartdir::NoValue) && ($self->{'m_closeData'}->[$i
] != 0)) {
return $self->{'m_closeData'}->[$i];
}
}
return $perlchartdir::NoValue;
}
#/
#/ A utility function to obtain the last valid position (that is, position not
#/ containing cd.NoValue) of a data series.
#/
#/ An array of numbers as input.
#/ The last valid position in the input array, or -1 if all positions
#/ are cd.NoValue.
sub lastIndex
{
my ($self, $data) = @_;
my $i = scalar(@$data) - 1;
while ($i >= 0) {
if ($data->[$i] != $perlchartdir::NoValue) {
last;
}
$i = $i - 1;
}
return $i;
}
#/
#/ Set the data used in the chart. If some of the data are not available, some artifical
#/ values should be used. For example, if the high and low values are not available, you
#/ may use closeData as highData and lowData.
#/
#/ An array of dates/times for the time intervals.
#/ The high values in the time intervals.
#/ The low values in the time intervals.
#/ The open values in the time intervals.
#/ The close values in the time intervals.
#/ The volume values in the time intervals.
#/ The number of leading time intervals that are not
#/ displayed in the chart. These intervals are typically used for computing
#/ indicators that require extra leading data, such as moving averages.
sub setData
{
my ($self, $timeStamps, $highData, $lowData, $openData, $closeData, $volData, $extraPoints)
= @_;
$self->{'m_timeStamps'} = $timeStamps;
$self->{'m_highData'} = $highData;
$self->{'m_lowData'} = $lowData;
$self->{'m_openData'} = $openData;
$self->{'m_closeData'} = $closeData;
if ($extraPoints > 0) {
$self->{'m_extraPoints'} = $extraPoints;
} else {
$self->{'m_extraPoints'} = 0;
}
#///////////////////////////////////////////////////////////////////////
# Auto-detect volume units
#///////////////////////////////////////////////////////////////////////
my $maxVol = new ArrayMath($volData)->max();
my $units = ["", "K", "M", "B"];
my $unitIndex = scalar(@$units) - 1;
while (($unitIndex > 0) && ($maxVol < 1000**$unitIndex)) {
$unitIndex = $unitIndex - 1;
}
$self->{'m_volData'} = new ArrayMath($volData)->div(1000**$unitIndex)->result();
$self->{'m_volUnit'} = $units->[$unitIndex];
}
#////////////////////////////////////////////////////////////////////////////
# Format x-axis labels
#////////////////////////////////////////////////////////////////////////////
sub setXLabels
{
my ($self, $a) = @_;
$a->setLabels2($self->{'m_timeStamps'});
if ($self->{'m_extraPoints'} < scalar(@{$self->{'m_timeStamps'}})) {
my $tickStep = int((scalar(@{$self->{'m_timeStamps'}}) - $self->{'m_extraPoints'}) *
$self->{'m_timeLabelSpacing'} / ($self->{'m_totalWidth'} - $self->{'m_leftMargin'} -
$self->{'m_rightMargin'})) + 1;
my $timeRangeInSeconds = $self->{'m_timeStamps'}->[scalar(@{$self->{'m_timeStamps'}}) - 1] -
$self->{'m_timeStamps'}->[$self->{'m_extraPoints'}];
my $secondsBetweenTicks = $timeRangeInSeconds / ($self->{'m_totalWidth'} -
$self->{'m_leftMargin'} - $self->{'m_rightMargin'}) * $self->{'m_timeLabelSpacing'};
if ($secondsBetweenTicks * (scalar(@{$self->{'m_timeStamps'}}) - $self->{'m_extraPoints'})
<= $timeRangeInSeconds) {
$tickStep = 1;
if (scalar(@{$self->{'m_timeStamps'}}) > 1) {
$secondsBetweenTicks = $self->{'m_timeStamps'}->[scalar(@{$self->{'m_timeStamps'}})
- 1] - $self->{'m_timeStamps'}->[scalar(@{$self->{'m_timeStamps'}}) - 2];
} else {
$secondsBetweenTicks = 86400;
}
}
if (($secondsBetweenTicks > 360 * 86400) || (($secondsBetweenTicks > 90 * 86400) && (
$timeRangeInSeconds >= 720 * 86400))) {
#yearly ticks
$a->setMultiFormat2(perlchartdir::StartOfYearFilter(), $self->{'m_yearFormat'},
$tickStep);
} elsif (($secondsBetweenTicks >= 30 * 86400) || (($secondsBetweenTicks > 7 * 86400) && (
$timeRangeInSeconds >= 60 * 86400))) {
#monthly ticks
my $monthBetweenTicks = int($secondsBetweenTicks / 31 / 86400) + 1;
$a->setMultiFormat(perlchartdir::StartOfYearFilter(), $self->{'m_firstMonthFormat'},
perlchartdir::StartOfMonthFilter($monthBetweenTicks), $self->{'m_otherMonthFormat'})
;
$a->setMultiFormat2(perlchartdir::StartOfMonthFilter(), "-", 1, 0);
} elsif (($secondsBetweenTicks >= 86400) || (($secondsBetweenTicks > 6 * 3600) && (
$timeRangeInSeconds >= 86400))) {
#daily ticks
$a->setMultiFormat(perlchartdir::StartOfMonthFilter(), $self->{'m_firstDayFormat'},
perlchartdir::StartOfDayFilter(1, 0.5), $self->{'m_otherDayFormat'}, $tickStep);
} else {
#hourly ticks
$a->setMultiFormat(perlchartdir::StartOfDayFilter(1, 0.5), $self->{'m_firstHourFormat'},
perlchartdir::StartOfHourFilter(1, 0.5), $self->{'m_otherHourFormat'}, $tickStep);
}
}
}
#////////////////////////////////////////////////////////////////////////////
# Create tool tip format string for showing OHLC data
#////////////////////////////////////////////////////////////////////////////
sub getHLOCToolTipFormat
{
my ($self) = @_;
return sprintf("title='%s Op:{open|%s}, Hi:{high|%s}, Lo:{low|%s}, Cl:{close|%s}'",
$self->getToolTipDateFormat(), $self->{'m_generalFormat'}, $self->{'m_generalFormat'},
$self->{'m_generalFormat'}, $self->{'m_generalFormat'});
}
#/
#/ Add the main chart - the chart that shows the HLOC data.
#/
#/ The height of the main chart in pixels.
#/ An XYChart object representing the main chart created.
sub addMainChart
{
my ($self, $height) = @_;
$self->{'m_mainChart'} = $self->addIndicator($height);
$self->setMainChart($self->{'m_mainChart'});
$self->{'m_mainChart'}->yAxis()->setMargin(2 * $self->{'m_yAxisMargin'});
if ($self->{'m_logScale'}) {
$self->{'m_mainChart'}->yAxis()->setLogScale();
} else {
$self->{'m_mainChart'}->yAxis()->setLinearScale();
}
return $self->{'m_mainChart'};
}
#/
#/ Add a candlestick layer to the main chart.
#/
#/ The candle color for an up day.
#/ The candle color for a down day.
#/ The CandleStickLayer created.
sub addCandleStick
{
my ($self, $upColor, $downColor) = @_;
$self->addOHLCLabel($upColor, $downColor, 1);
my $ret = $self->{'m_mainChart'}->addCandleStickLayer($self->{'m_highData'},
$self->{'m_lowData'}, $self->{'m_openData'}, $self->{'m_closeData'}, $upColor, $downColor);
$ret->setHTMLImageMap("", "", $self->getHLOCToolTipFormat());
if (scalar(@{$self->{'m_highData'}}) - $self->{'m_extraPoints'} > 60) {
$ret->setDataGap(0);
}
if (scalar(@{$self->{'m_highData'}}) > $self->{'m_extraPoints'}) {
my $expectedWidth = ($self->{'m_totalWidth'} - $self->{'m_leftMargin'} -
$self->{'m_rightMargin'}) / (scalar(@{$self->{'m_highData'}}) - $self->{'m_extraPoints'}
);
if ($expectedWidth <= 5) {
$ret->setDataWidth($expectedWidth + 1 - $expectedWidth % 2);
}
}
return $ret;
}
#/
#/ Add a HLOC layer to the main chart.
#/
#/ The color of the HLOC symbol for an up day.
#/ The color of the HLOC symbol for a down day.
#/ The HLOCLayer created.
sub addHLOC
{
my ($self, $upColor, $downColor) = @_;
$self->addOHLCLabel($upColor, $downColor, 0);
my $ret = $self->{'m_mainChart'}->addHLOCLayer($self->{'m_highData'}, $self->{'m_lowData'},
$self->{'m_openData'}, $self->{'m_closeData'});
$ret->setColorMethod($perlchartdir::HLOCUpDown, $upColor, $downColor);
$ret->setHTMLImageMap("", "", $self->getHLOCToolTipFormat());
$ret->setDataGap(0);
return $ret;
}
sub addOHLCLabel
{
my ($self, $upColor, $downColor, $candleStickMode) = @_;
my $i = $self->lastIndex($self->{'m_closeData'});
if ($i >= 0) {
my $openValue = $perlchartdir::NoValue;
my $closeValue = $perlchartdir::NoValue;
my $highValue = $perlchartdir::NoValue;
my $lowValue = $perlchartdir::NoValue;
if ($i < scalar(@{$self->{'m_openData'}})) {
$openValue = $self->{'m_openData'}->[$i];
}
if ($i < scalar(@{$self->{'m_closeData'}})) {
$closeValue = $self->{'m_closeData'}->[$i];
}
if ($i < scalar(@{$self->{'m_highData'}})) {
$highValue = $self->{'m_highData'}->[$i];
}
if ($i < scalar(@{$self->{'m_lowData'}})) {
$lowValue = $self->{'m_lowData'}->[$i];
}
my $openLabel = "";
my $closeLabel = "";
my $highLabel = "";
my $lowLabel = "";
my $delim = "";
if ($openValue != $perlchartdir::NoValue) {
$openLabel = sprintf("Op:%s", $self->formatValue($openValue, $self->{'m_generalFormat'})
);
$delim = ", ";
}
if ($highValue != $perlchartdir::NoValue) {
$highLabel = sprintf("%sHi:%s", $delim, $self->formatValue($highValue,
$self->{'m_generalFormat'}));
$delim = ", ";
}
if ($lowValue != $perlchartdir::NoValue) {
$lowLabel = sprintf("%sLo:%s", $delim, $self->formatValue($lowValue,
$self->{'m_generalFormat'}));
$delim = ", ";
}
if ($closeValue != $perlchartdir::NoValue) {
$closeLabel = sprintf("%sCl:%s", $delim, $self->formatValue($closeValue,
$self->{'m_generalFormat'}));
$delim = ", ";
}
my $label = "$openLabel$highLabel$lowLabel$closeLabel";
my $useUpColor = ($closeValue >= $openValue);
if ($candleStickMode != 1) {
my $closeChanges = new ArrayMath($self->{'m_closeData'})->delta()->result();
my $lastChangeIndex = $self->lastIndex($closeChanges);
$useUpColor = ($lastChangeIndex < 0);
if ($useUpColor != 1) {
$useUpColor = ($closeChanges->[$lastChangeIndex] >= 0);
}
}
my $udcolor = $downColor;
if ($useUpColor) {
$udcolor = $upColor;
}
$self->{'m_mainChart'}->getLegend()->addKey($label, $udcolor);
}
}
#/
#/ Add a closing price line on the main chart.
#/
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addCloseLine
{
my ($self, $color) = @_;
return $self->addLineIndicator2($self->{'m_mainChart'}, $self->{'m_closeData'}, $color,
"Closing Price");
}
#/
#/ Add a weight close line on the main chart.
#/
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addWeightedClose
{
my ($self, $color) = @_;
return $self->addLineIndicator2($self->{'m_mainChart'}, new ArrayMath($self->{'m_highData'}
)->add($self->{'m_lowData'})->add($self->{'m_closeData'})->add($self->{'m_closeData'})->div(
4)->result(), $color, "Weighted Close");
}
#/
#/ Add a typical price line on the main chart.
#/
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addTypicalPrice
{
my ($self, $color) = @_;
return $self->addLineIndicator2($self->{'m_mainChart'}, new ArrayMath($self->{'m_highData'}
)->add($self->{'m_lowData'})->add($self->{'m_closeData'})->div(3)->result(), $color,
"Typical Price");
}
#/
#/ Add a median price line on the main chart.
#/
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addMedianPrice
{
my ($self, $color) = @_;
return $self->addLineIndicator2($self->{'m_mainChart'}, new ArrayMath($self->{'m_highData'}
)->add($self->{'m_lowData'})->div(2)->result(), $color, "Median Price");
}
#/
#/ Add a simple moving average line on the main chart.
#/
#/ The moving average period
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addSimpleMovingAvg
{
my ($self, $period, $color) = @_;
my $label = "SMA ($period)";
return $self->addLineIndicator2($self->{'m_mainChart'}, new ArrayMath($self->{'m_closeData'}
)->movAvg($period)->result(), $color, $label);
}
#/
#/ Add an exponential moving average line on the main chart.
#/
#/ The moving average period
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addExpMovingAvg
{
my ($self, $period, $color) = @_;
my $label = "EMA ($period)";
return $self->addLineIndicator2($self->{'m_mainChart'}, new ArrayMath($self->{'m_closeData'}
)->expAvg(2.0 / ($period + 1))->result(), $color, $label);
}
#/
#/ Add a triangular moving average line on the main chart.
#/
#/ The moving average period
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addTriMovingAvg
{
my ($self, $period, $color) = @_;
my $label = "TMA ($period)";
return $self->addLineIndicator2($self->{'m_mainChart'}, new ArrayMath(
$self->computeTriMovingAvg($self->{'m_closeData'}, $period))->result(), $color, $label);
}
#/
#/ Add a weighted moving average line on the main chart.
#/
#/ The moving average period
#/ The color of the line.
#/ The LineLayer object representing the line created.
sub addWeightedMovingAvg
{
my ($self, $period, $color) = @_;
my $label = "WMA ($period)";
return $self->addLineIndicator2($self->{'m_mainChart'}, new ArrayMath(
$self->computeWeightedMovingAvg($self->{'m_closeData'}, $period))->result(), $color, $label)
;
}
#/
#/ Add a parabolic SAR indicator to the main chart.
#/
#/ Initial acceleration factor
#/ Acceleration factor increment
#/ Maximum acceleration factor
#/ The symbol used to plot the parabolic SAR
#/ The symbol size in pixels
#/ The fill color of the symbol
#/ The edge color of the symbol
#/ The LineLayer object representing the layer created.
sub addParabolicSAR
{
my ($self, $accInitial, $accIncrement, $accMaximum, $symbolType, $symbolSize, $fillColor,
$edgeColor) = @_;
my $isLong = 1;
my $acc = $accInitial;
my $extremePoint = 0;
my $psar = [(0) x scalar(@{$self->{'m_lowData'}})];
my $i_1 = -1;
my $i_2 = -1;
for(my $i = 0; $i < scalar(@{$self->{'m_lowData'}}); ++$i) {
$psar->[$i] = $perlchartdir::NoValue;
if (($self->{'m_lowData'}->[$i] != $perlchartdir::NoValue) && ($self->{'m_highData'}->[$i]
!= $perlchartdir::NoValue)) {
if (($i_1 >= 0) && ($i_2 < 0)) {
if ($self->{'m_lowData'}->[$i_1] <= $self->{'m_lowData'}->[$i]) {
$psar->[$i] = $self->{'m_lowData'}->[$i_1];
$isLong = 1;
if ($self->{'m_highData'}->[$i_1] > $self->{'m_highData'}->[$i]) {
$extremePoint = $self->{'m_highData'}->[$i_1];
} else {
$extremePoint = $self->{'m_highData'}->[$i];
}
} else {
$extremePoint = $self->{'m_lowData'}->[$i];
$isLong = 0;
if ($self->{'m_highData'}->[$i_1] > $self->{'m_highData'}->[$i]) {
$psar->[$i] = $self->{'m_highData'}->[$i_1];
} else {
$psar->[$i] = $self->{'m_highData'}->[$i];
}
}
} elsif (($i_1 >= 0) && ($i_2 >= 0)) {
if ($acc > $accMaximum) {
$acc = $accMaximum;
}
$psar->[$i] = $psar->[$i_1] + $acc * ($extremePoint - $psar->[$i_1]);
if ($isLong) {
if ($self->{'m_lowData'}->[$i] < $psar->[$i]) {
$isLong = 0;
$psar->[$i] = $extremePoint;
$extremePoint = $self->{'m_lowData'}->[$i];
$acc = $accInitial;
} else {
if ($self->{'m_highData'}->[$i] > $extremePoint) {
$extremePoint = $self->{'m_highData'}->[$i];
$acc = $acc + $accIncrement;
}
if ($self->{'m_lowData'}->[$i_1] < $psar->[$i]) {
$psar->[$i] = $self->{'m_lowData'}->[$i_1];
}
if ($self->{'m_lowData'}->[$i_2] < $psar->[$i]) {
$psar->[$i] = $self->{'m_lowData'}->[$i_2];
}
}
} else {
if ($self->{'m_highData'}->[$i] > $psar->[$i]) {
$isLong = 1;
$psar->[$i] = $extremePoint;
$extremePoint = $self->{'m_highData'}->[$i];
$acc = $accInitial;
} else {
if ($self->{'m_lowData'}->[$i] < $extremePoint) {
$extremePoint = $self->{'m_lowData'}->[$i];
$acc = $acc + $accIncrement;
}
if ($self->{'m_highData'}->[$i_1] > $psar->[$i]) {
$psar->[$i] = $self->{'m_highData'}->[$i_1];
}
if ($self->{'m_highData'}->[$i_2] > $psar->[$i]) {
$psar->[$i] = $self->{'m_highData'}->[$i_2];
}
}
}
}
$i_2 = $i_1;
$i_1 = $i;
}
}
my $ret = $self->addLineIndicator2($self->{'m_mainChart'}, $psar, $fillColor, "Parabolic SAR");
$ret->setLineWidth(0);
$ret->addDataSet($psar)->setDataSymbol($symbolType, $symbolSize, $fillColor, $edgeColor);
return $ret;
}
#/
#/ Add a comparison line to the main price chart.
#/
#/ The data series to compare to
#/ The color of the comparison line
#/ The name of the comparison line
#/ The LineLayer object representing the line layer created.
sub addComparison
{
my ($self, $data, $color, $name) = @_;
my $firstIndex = $self->{'m_extraPoints'};
while (($firstIndex < scalar(@$data)) && ($firstIndex < scalar(@{$self->{'m_closeData'}}))) {
if (($data->[$firstIndex] != $perlchartdir::NoValue) && ($self->{'m_closeData'}->[
$firstIndex] != $perlchartdir::NoValue) && ($data->[$firstIndex] != 0) && (
$self->{'m_closeData'}->[$firstIndex] != 0)) {
last;
}
$firstIndex = $firstIndex + 1;
}
if (($firstIndex >= scalar(@$data)) || ($firstIndex >= scalar(@{$self->{'m_closeData'}}))) {
return undef;
}
my $scaleFactor = $self->{'m_closeData'}->[$firstIndex] / $data->[$firstIndex];
my $layer = $self->{'m_mainChart'}->addLineLayer(new ArrayMath($data)->mul($scaleFactor
)->result(), $perlchartdir::Transparent);
$layer->setHTMLImageMap("{disable}");
my $a = $self->{'m_mainChart'}->addAxis($perlchartdir::Right, 0);
$a->setColors($perlchartdir::Transparent, $perlchartdir::Transparent);
$a->syncAxis($self->{'m_mainChart'}->yAxis(), 1 / $scaleFactor, 0);
my $ret = $self->addLineIndicator2($self->{'m_mainChart'}, $data, $color, $name);
$ret->setUseYAxis($a);
return $ret;
}
#/
#/ Display percentage axis scale
#/
#/ The Axis object representing the percentage axis.
sub setPercentageAxis
{
my ($self) = @_;
my $firstClose = $self->firstCloseValue();
if ($firstClose == $perlchartdir::NoValue) {
return undef;
}
my $axisAlign = $perlchartdir::Left;
if ($self->{'m_axisOnRight'}) {
$axisAlign = $perlchartdir::Right;
}
my $ret = $self->{'m_mainChart'}->addAxis($axisAlign, 0);
$self->configureYAxis($ret, 300);
$ret->syncAxis($self->{'m_mainChart'}->yAxis(), 100 / $firstClose);
$ret->setRounding(0, 0);
$ret->setLabelFormat("{={value}-100|@}%");
$self->{'m_mainChart'}->yAxis()->setColors($perlchartdir::Transparent,
$perlchartdir::Transparent);
$self->{'m_mainChart'}->getPlotArea()->setGridAxis(undef, $ret);
return $ret;
}
#/
#/ Add a generic band to the main finance chart. This method is used internally by other methods to add
#/ various bands (eg. Bollinger band, Donchian channels, etc).
#/
#/ The data series for the upper band line.
#/ The data series for the lower band line.
#/ The color of the upper and lower band line.
#/ The color to fill the region between the upper and lower band lines.
#/ The name of the band.
#/ An InterLineLayer object representing the filled region.
sub addBand
{
my ($self, $upperLine, $lowerLine, $lineColor, $fillColor, $name) = @_;
my $i = scalar(@$upperLine) - 1;
if ($i >= scalar(@$lowerLine)) {
$i = scalar(@$lowerLine) - 1;
}
while ($i >= 0) {
if (($upperLine->[$i] != $perlchartdir::NoValue) && ($lowerLine->[$i] !=
$perlchartdir::NoValue)) {
$name = sprintf("%s: %s - %s", $name, $self->formatValue($lowerLine->[$i],
$self->{'m_generalFormat'}), $self->formatValue($upperLine->[$i],
$self->{'m_generalFormat'}));
last;
}
$i = $i - 1;
}
my $uLayer = $self->{'m_mainChart'}->addLineLayer($upperLine, $lineColor, $name);
my $lLayer = $self->{'m_mainChart'}->addLineLayer($lowerLine, $lineColor);
return $self->{'m_mainChart'}->addInterLineLayer($uLayer->getLine(), $lLayer->getLine(),
$fillColor);
}
#/
#/ Add a Bollinger band on the main chart.
#/
#/ The period to compute the band.
#/ The half-width of the band in terms multiples of standard deviation. Typically 2 is used.
#/ The color of the lines defining the upper and lower limits.
#/ The color to fill the regional within the band.
#/ The InterLineLayer object representing the band created.
sub addBollingerBand
{
my ($self, $period, $bandWidth, $lineColor, $fillColor) = @_;
#Bollinger Band is moving avg +/- (width * moving std deviation)
my $stdDev = new ArrayMath($self->{'m_closeData'})->movStdDev($period)->mul($bandWidth)->result(
);
my $movAvg = new ArrayMath($self->{'m_closeData'})->movAvg($period)->result();
my $label = "Bollinger ($period, $bandWidth)";
return $self->addBand(new ArrayMath($movAvg)->add($stdDev)->result(), new ArrayMath($movAvg
)->sub($stdDev)->selectGTZ(undef, 0)->result(), $lineColor, $fillColor, $label);
}
#/
#/ Add a Donchian channel on the main chart.
#/
#/ The period to compute the band.
#/ The color of the lines defining the upper and lower limits.
#/ The color to fill the regional within the band.
#/ The InterLineLayer object representing the band created.
sub addDonchianChannel
{
my ($self, $period, $lineColor, $fillColor) = @_;
#Donchian Channel is the zone between the moving max and moving min
my $label = "Donchian ($period)";
return $self->addBand(new ArrayMath($self->{'m_highData'})->movMax($period)->result(),
new ArrayMath($self->{'m_lowData'})->movMin($period)->result(), $lineColor, $fillColor,
$label);
}
#/
#/ Add a price envelop on the main chart. The price envelop is a defined as a ratio around a
#/ moving average. For example, a ratio of 0.2 means 20% above and below the moving average.
#/
#/ The period for the moving average.
#/ The ratio above and below the moving average.
#/ The color of the lines defining the upper and lower limits.
#/ The color to fill the regional within the band.
#/ The InterLineLayer object representing the band created.
sub addEnvelop
{
my ($self, $period, $range, $lineColor, $fillColor) = @_;
#Envelop is moving avg +/- percentage
my $movAvg = new ArrayMath($self->{'m_closeData'})->movAvg($period)->result();
my $label = sprintf("Envelop (SMA %s +/- %s%)", $period, int($range * 100));
return $self->addBand(new ArrayMath($movAvg)->mul(1 + $range)->result(), new ArrayMath($movAvg
)->mul(1 - $range)->result(), $lineColor, $fillColor, $label);
}
#/
#/ Add a volume bar chart layer on the main chart.
#/
#/ The height of the bar chart layer in pixels.
#/ The color to used on an 'up' day. An 'up' day is a day where
#/ the closing price is higher than that of the previous day.
#/ The color to used on a 'down' day. A 'down' day is a day
#/ where the closing price is lower than that of the previous day.
#/ The color to used on a 'flat' day. A 'flat' day is a day
#/ where the closing price is the same as that of the previous day.
#/ The XYChart object representing the chart created.
sub addVolBars
{
my ($self, $height, $upColor, $downColor, $flatColor) = @_;
return $self->addVolBars2($self->{'m_mainChart'}, $height, $upColor, $downColor, $flatColor);
}
sub addVolBars2
{
my ($self, $c, $height, $upColor, $downColor, $flatColor) = @_;
my $barLayer = $c->addBarLayer2($perlchartdir::Overlay);
$barLayer->setBorderColor($perlchartdir::Transparent);
if ($c == $self->{'m_mainChart'}) {
$self->configureYAxis($c->yAxis2(), $height);
my $topMargin = $c->getDrawArea()->getHeight() - $self->{'m_topMargin'} -
$self->{'m_bottomMargin'} - $height + $self->{'m_yAxisMargin'};
if ($topMargin < 0) {
$topMargin = 0;
}
$c->yAxis2()->setTopMargin($topMargin);
$barLayer->setUseYAxis2();
}
my $a = $c->yAxis2();
if ($c != $self->{'m_mainChart'}) {
$a = $c->yAxis();
}
if (new ArrayMath($self->{'m_volData'})->max() < 10) {
$a->setLabelFormat(sprintf("{value|1}%s", $self->{'m_volUnit'}));
} else {
$a->setLabelFormat(sprintf("{value}%s", $self->{'m_volUnit'}));
}
my $closeChange = new ArrayMath($self->{'m_closeData'})->delta()->result();
my $i = $self->lastIndex($self->{'m_volData'});
my $label = "Vol";
if ($i >= 0) {
$label = sprintf("%s: %s%s", $label, $self->formatValue($self->{'m_volData'}->[$i],
$self->{'m_generalFormat'}), $self->{'m_volUnit'});
$closeChange->[0] = 0;
}
my $upDS = $barLayer->addDataSet(new ArrayMath($self->{'m_volData'})->selectGTZ($closeChange
)->result(), $upColor);
my $dnDS = $barLayer->addDataSet(new ArrayMath($self->{'m_volData'})->selectLTZ($closeChange
)->result(), $downColor);
my $flatDS = $barLayer->addDataSet(new ArrayMath($self->{'m_volData'})->selectEQZ($closeChange
)->result(), $flatColor);
if (($i < 0) || ($closeChange->[$i] == 0) || ($closeChange->[$i] == $perlchartdir::NoValue)) {
$flatDS->setDataName($label);
} elsif ($closeChange->[$i] > 0) {
$upDS->setDataName($label);
} else {
$dnDS->setDataName($label);
}
return $barLayer;
}
#/
#/ Add a blank indicator chart to the finance chart. Used internally to add other indicators.
#/ Override to change the default formatting (eg. axis fonts, etc.) of the various indicators.
#/
#/ The height of the chart in pixels.
#/ The XYChart object representing the chart created.
sub addIndicator
{
my ($self, $height) = @_;
#create a new chart object
my $ret = new XYChart($self->{'m_totalWidth'}, $height + $self->{'m_topMargin'} +
$self->{'m_bottomMargin'}, $perlchartdir::Transparent);
$ret->setTrimData($self->{'m_extraPoints'});
if (defined($self->{'m_currentChart'})) {
#if there is a chart before the newly created chart, disable its x-axis, and copy
#its x-axis labels to the new chart
$self->{'m_currentChart'}->xAxis()->setColors($perlchartdir::Transparent,
$perlchartdir::Transparent);
$ret->xAxis()->copyAxis($self->{'m_currentChart'}->xAxis());
#add chart to MultiChart and update the total height
$self->addChart(0, $self->{'m_totalHeight'} + $self->{'m_plotAreaGap'}, $ret);
$self->{'m_totalHeight'} = $self->{'m_totalHeight'} + $height + 1 + $self->{'m_plotAreaGap'}
;
} else {
#no existing chart - create the x-axis labels from scratch
$self->setXLabels($ret->xAxis());
#add chart to MultiChart and update the total height
$self->addChart(0, $self->{'m_totalHeight'}, $ret);
$self->{'m_totalHeight'} = $self->{'m_totalHeight'} + $height + 1;
}
#the newly created chart becomes the current chart
$self->{'m_currentChart'} = $ret;
#update the size
$self->setSize($self->{'m_totalWidth'}, $self->{'m_totalHeight'} + $self->{'m_topMargin'} +
$self->{'m_bottomMargin'});
#configure the plot area
$ret->setPlotArea($self->{'m_leftMargin'}, $self->{'m_topMargin'}, $self->{'m_totalWidth'} -
$self->{'m_leftMargin'} - $self->{'m_rightMargin'}, $height, $self->{'m_plotAreaBgColor'},
-1, $self->{'m_plotAreaBorder'})->setGridColor($self->{'m_majorHGridColor'},
$self->{'m_majorVGridColor'}, $self->{'m_minorHGridColor'}, $self->{'m_minorVGridColor'});
$ret->setAntiAlias($self->{'m_antiAlias'});
#configure legend box
my $box = $ret->addLegend($self->{'m_leftMargin'}, $self->{'m_topMargin'}, 0,
$self->{'m_legendFont'}, $self->{'m_legendFontSize'});
$box->setFontColor($self->{'m_legendFontColor'});
$box->setBackground($self->{'m_legendBgColor'});
$box->setMargin2(5, 0, 2, 1);
$box->setSize($self->{'m_totalWidth'} - $self->{'m_leftMargin'} - $self->{'m_rightMargin'} + 1,
0);
#configure x-axis
my $a = $ret->xAxis();
$a->setIndent(1);
$a->setTickLength(2, 0);
$a->setColors($perlchartdir::Transparent, $self->{'m_xAxisFontColor'},
$self->{'m_xAxisFontColor'}, $self->{'m_xAxisFontColor'});
$a->setLabelStyle($self->{'m_xAxisFont'}, $self->{'m_xAxisFontSize'},
$self->{'m_xAxisFontColor'}, $self->{'m_xAxisFontAngle'});
#configure y-axis
$ret->setYAxisOnRight($self->{'m_axisOnRight'});
$self->configureYAxis($ret->yAxis(), $height);
return $ret;
}
sub configureYAxis
{
my ($self, $a, $height) = @_;
$a->setAutoScale(0, 0.05, 0);
if ($height < 100) {
$a->setTickDensity(15);
}
$a->setMargin($self->{'m_yAxisMargin'});
$a->setLabelStyle($self->{'m_yAxisFont'}, $self->{'m_yAxisFontSize'},
$self->{'m_yAxisFontColor'}, 0);
$a->setTickLength(-4, -2);
$a->setColors($perlchartdir::Transparent, $self->{'m_yAxisFontColor'},
$self->{'m_yAxisFontColor'}, $self->{'m_yAxisFontColor'});
}
#/
#/ Add a generic line indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The data series of the indicator line.
#/ The color of the indicator line.
#/ The name of the indicator.
#/ The XYChart object representing the chart created.
sub addLineIndicator
{
my ($self, $height, $data, $color, $name) = @_;
my $c = $self->addIndicator($height);
$self->addLineIndicator2($c, $data, $color, $name);
return $c;
}
#/
#/ Add a line to an existing indicator chart.
#/
#/ The indicator chart to add the line to.
#/ The data series of the indicator line.
#/ The color of the indicator line.
#/ The name of the indicator.
#/ The LineLayer object representing the line created.
sub addLineIndicator2
{
my ($self, $c, $data, $color, $name) = @_;
return $c->addLineLayer($data, $color, $self->formatIndicatorLabel($name, $data));
}
#/
#/ Add a generic bar indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The data series of the indicator bars.
#/ The color of the indicator bars.
#/ The name of the indicator.
#/ The XYChart object representing the chart created.
sub addBarIndicator
{
my ($self, $height, $data, $color, $name) = @_;
my $c = $self->addIndicator($height);
$self->addBarIndicator2($c, $data, $color, $name);
return $c;
}
#/
#/ Add a bar layer to an existing indicator chart.
#/
#/ The indicator chart to add the bar layer to.
#/ The data series of the indicator bars.
#/ The color of the indicator bars.
#/ The name of the indicator.
#/ The BarLayer object representing the bar layer created.
sub addBarIndicator2
{
my ($self, $c, $data, $color, $name) = @_;
my $layer = $c->addBarLayer($data, $color, $self->formatIndicatorLabel($name, $data));
$layer->setBorderColor($perlchartdir::Transparent);
return $layer;
}
#/
#/ Add an upper/lower threshold range to an existing indicator chart.
#/
#/ The indicator chart to add the threshold range to.
#/ The line layer that the threshold range applies to.
#/ The upper threshold.
#/ The color to fill the region of the line that is above the
#/ upper threshold.
#/ The lower threshold.
#/ The color to fill the region of the line that is below
#/ the lower threshold.
sub addThreshold
{
my ($self, $c, $layer, $topRange, $topColor, $bottomRange, $bottomColor) = @_;
my $topMark = $c->yAxis()->addMark($topRange, $topColor, $self->formatValue($topRange,
$self->{'m_generalFormat'}));
my $bottomMark = $c->yAxis()->addMark($bottomRange, $bottomColor, $self->formatValue(
$bottomRange, $self->{'m_generalFormat'}));
$c->addInterLineLayer($layer->getLine(), $topMark->getLine(), $topColor,
$perlchartdir::Transparent);
$c->addInterLineLayer($layer->getLine(), $bottomMark->getLine(), $perlchartdir::Transparent,
$bottomColor);
}
sub formatIndicatorLabel
{
my ($self, $name, $data) = @_;
my $i = $self->lastIndex($data);
if (!defined($name)) {
return $name;
}
if (($name eq "") || ($i < 0)) {
return $name;
}
my $ret = sprintf("%s: %s", $name, $self->formatValue($data->[$i], $self->{'m_generalFormat'}));
return $ret;
}
#/
#/ Add an Accumulation/Distribution indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addAccDist
{
my ($self, $height, $color) = @_;
#Close Location Value = ((C - L) - (H - C)) / (H - L)
#Accumulation Distribution Line = Accumulation of CLV * volume
my $range = new ArrayMath($self->{'m_highData'})->sub($self->{'m_lowData'})->result();
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->mul(2)->sub(
$self->{'m_lowData'})->sub($self->{'m_highData'})->mul($self->{'m_volData'})->financeDiv(
$range, 0)->acc()->result(), $color, "Accumulation/Distribution");
}
sub computeAroonUp
{
my ($self, $period) = @_;
my $aroonUp = [(0) x scalar(@{$self->{'m_highData'}})];
for(my $i = 0; $i < scalar(@{$self->{'m_highData'}}); ++$i) {
my $highValue = $self->{'m_highData'}->[$i];
if ($highValue == $perlchartdir::NoValue) {
$aroonUp->[$i] = $perlchartdir::NoValue;
} else {
my $currentIndex = $i;
my $highCount = $period;
my $count = $period;
while (($count > 0) && ($currentIndex >= $count)) {
$currentIndex = $currentIndex - 1;
my $currentValue = $self->{'m_highData'}->[$currentIndex];
if ($currentValue != $perlchartdir::NoValue) {
$count = $count - 1;
if ($currentValue > $highValue) {
$highValue = $currentValue;
$highCount = $count;
}
}
}
if ($count > 0) {
$aroonUp->[$i] = $perlchartdir::NoValue;
} else {
$aroonUp->[$i] = $highCount * 100.0 / $period;
}
}
}
return $aroonUp;
}
sub computeAroonDn
{
my ($self, $period) = @_;
my $aroonDn = [(0) x scalar(@{$self->{'m_lowData'}})];
for(my $i = 0; $i < scalar(@{$self->{'m_lowData'}}); ++$i) {
my $lowValue = $self->{'m_lowData'}->[$i];
if ($lowValue == $perlchartdir::NoValue) {
$aroonDn->[$i] = $perlchartdir::NoValue;
} else {
my $currentIndex = $i;
my $lowCount = $period;
my $count = $period;
while (($count > 0) && ($currentIndex >= $count)) {
$currentIndex = $currentIndex - 1;
my $currentValue = $self->{'m_lowData'}->[$currentIndex];
if ($currentValue != $perlchartdir::NoValue) {
$count = $count - 1;
if ($currentValue < $lowValue) {
$lowValue = $currentValue;
$lowCount = $count;
}
}
}
if ($count > 0) {
$aroonDn->[$i] = $perlchartdir::NoValue;
} else {
$aroonDn->[$i] = $lowCount * 100.0 / $period;
}
}
}
return $aroonDn;
}
#/
#/ Add an Aroon Up/Down indicators chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicators.
#/ The color of the Aroon Up indicator line.
#/ The color of the Aroon Down indicator line.
#/ The XYChart object representing the chart created.
sub addAroon
{
my ($self, $height, $period, $upColor, $downColor) = @_;
my $c = $self->addIndicator($height);
$self->addLineIndicator2($c, $self->computeAroonUp($period), $upColor, "Aroon Up");
$self->addLineIndicator2($c, $self->computeAroonDn($period), $downColor, "Aroon Down");
$c->yAxis()->setLinearScale(0, 100);
return $c;
}
#/
#/ Add an Aroon Oscillator indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addAroonOsc
{
my ($self, $height, $period, $color) = @_;
my $label = "Aroon Oscillator ($period)";
my $c = $self->addLineIndicator($height, new ArrayMath($self->computeAroonUp($period))->sub(
$self->computeAroonDn($period))->result(), $color, $label);
$c->yAxis()->setLinearScale(-100, 100);
return $c;
}
sub computeTrueRange
{
my ($self) = @_;
my $previousClose = new ArrayMath($self->{'m_closeData'})->shift()->result();
my $ret = new ArrayMath($self->{'m_highData'})->sub($self->{'m_lowData'})->result();
my $temp = 0;
for(my $i = 0; $i < scalar(@{$self->{'m_highData'}}); ++$i) {
if (($ret->[$i] != $perlchartdir::NoValue) && ($previousClose->[$i] !=
$perlchartdir::NoValue)) {
$temp = abs($self->{'m_highData'}->[$i] - $previousClose->[$i]);
if ($temp > $ret->[$i]) {
$ret->[$i] = $temp;
}
$temp = abs($previousClose->[$i] - $self->{'m_lowData'}->[$i]);
if ($temp > $ret->[$i]) {
$ret->[$i] = $temp;
}
}
}
return $ret;
}
#/
#/ Add an Average Directional Index indicators chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the Positive Directional Index line.
#/ The color of the Negatuve Directional Index line.
#/ The color of the Average Directional Index line.
#/ The XYChart object representing the chart created.
sub addADX
{
my ($self, $height, $period, $posColor, $negColor, $color) = @_;
#pos/neg directional movement
my $pos = new ArrayMath($self->{'m_highData'})->delta()->selectGTZ();
my $neg = new ArrayMath($self->{'m_lowData'})->delta()->mul(-1)->selectGTZ();
my $delta = new ArrayMath($pos->result())->sub($neg->result())->result();
$pos->selectGTZ($delta);
$neg->selectLTZ($delta);
#pos/neg directional index
my $tr = $self->computeTrueRange();
$pos->financeDiv($tr, 0.25)->mul(100)->expAvg(2.0 / ($period + 1));
$neg->financeDiv($tr, 0.25)->mul(100)->expAvg(2.0 / ($period + 1));
#directional movement index ??? what happen if division by zero???
my $totalDM = new ArrayMath($pos->result())->add($neg->result())->result();
my $dx = new ArrayMath($pos->result())->sub($neg->result())->abs()->financeDiv($totalDM, 0
)->mul(100)->expAvg(2.0 / ($period + 1));
my $c = $self->addIndicator($height);
my $label1 = "+DI ($period)";
my $label2 = "-DI ($period)";
my $label3 = "ADX ($period)";
$self->addLineIndicator2($c, $pos->result(), $posColor, $label1);
$self->addLineIndicator2($c, $neg->result(), $negColor, $label2);
$self->addLineIndicator2($c, $dx->result(), $color, $label3);
return $c;
}
#/
#/ Add an Average True Range indicators chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the True Range line.
#/ The color of the Average True Range line.
#/ The XYChart object representing the chart created.
sub addATR
{
my ($self, $height, $period, $color1, $color2) = @_;
my $trueRange = $self->computeTrueRange();
my $c = $self->addLineIndicator($height, $trueRange, $color1, "True Range");
my $label = "Average True Range ($period)";
$self->addLineIndicator2($c, new ArrayMath($trueRange)->expAvg(2.0 / ($period + 1))->result(),
$color2, $label);
return $c;
}
#/
#/ Add a Bollinger Band Width indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The band width to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addBollingerWidth
{
my ($self, $height, $period, $width, $color) = @_;
my $label = "Bollinger Width ($period, $width)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->movStdDev($period
)->mul($width * 2)->result(), $color, $label);
}
#/
#/ Add a Community Channel Index indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The distance beween the middle line and the upper and lower threshold lines.
#/ The fill color when the indicator exceeds the upper threshold line.
#/ The fill color when the indicator falls below the lower threshold line.
#/ The XYChart object representing the chart created.
sub addCCI
{
my ($self, $height, $period, $color, $range, $upColor, $downColor) = @_;
#typical price
my $tp = new ArrayMath($self->{'m_highData'})->add($self->{'m_lowData'})->add(
$self->{'m_closeData'})->div(3)->result();
#simple moving average of typical price
my $smvtp = new ArrayMath($tp)->movAvg($period)->result();
#compute mean deviation
my $movMeanDev = [(0) x scalar(@$smvtp)];
for(my $i = 0; $i < scalar(@$smvtp); ++$i) {
my $avg = $smvtp->[$i];
if ($avg == $perlchartdir::NoValue) {
$movMeanDev->[$i] = $perlchartdir::NoValue;
} else {
my $currentIndex = $i;
my $count = $period - 1;
my $acc = 0;
while (($count > 0) && ($currentIndex >= $count)) {
$currentIndex = $currentIndex - 1;
my $currentValue = $tp->[$currentIndex];
if ($currentValue != $perlchartdir::NoValue) {
$count = $count - 1;
$acc = $acc + abs($avg - $currentValue);
}
}
if ($count > 0) {
$movMeanDev->[$i] = $perlchartdir::NoValue;
} else {
$movMeanDev->[$i] = $acc / $period;
}
}
}
my $c = $self->addIndicator($height);
my $label = "CCI ($period)";
my $layer = $self->addLineIndicator2($c, new ArrayMath($tp)->sub($smvtp)->financeDiv(
$movMeanDev, 0)->div(0.015)->result(), $color, $label);
$self->addThreshold($c, $layer, $range, $upColor, -$range, $downColor);
return $c;
}
#/
#/ Add a Chaikin Money Flow indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addChaikinMoneyFlow
{
my ($self, $height, $period, $color) = @_;
my $range = new ArrayMath($self->{'m_highData'})->sub($self->{'m_lowData'})->result();
my $volAvg = new ArrayMath($self->{'m_volData'})->movAvg($period)->result();
my $label = "Chaikin Money Flow ($period)";
return $self->addBarIndicator($height, new ArrayMath($self->{'m_closeData'})->mul(2)->sub(
$self->{'m_lowData'})->sub($self->{'m_highData'})->mul($self->{'m_volData'})->financeDiv(
$range, 0)->movAvg($period)->financeDiv($volAvg, 0)->result(), $color, $label);
}
#/
#/ Add a Chaikin Oscillator indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addChaikinOscillator
{
my ($self, $height, $color) = @_;
#first compute acc/dist line
my $range = new ArrayMath($self->{'m_highData'})->sub($self->{'m_lowData'})->result();
my $accdist = new ArrayMath($self->{'m_closeData'})->mul(2)->sub($self->{'m_lowData'})->sub(
$self->{'m_highData'})->mul($self->{'m_volData'})->financeDiv($range, 0)->acc()->result();
#chaikin osc = exp3(accdist) - exp10(accdist)
my $expAvg10 = new ArrayMath($accdist)->expAvg(2.0 / (10 + 1))->result();
return $self->addLineIndicator($height, new ArrayMath($accdist)->expAvg(2.0 / (3 + 1))->sub(
$expAvg10)->result(), $color, "Chaikin Oscillator");
}
#/
#/ Add a Chaikin Volatility indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to smooth the range.
#/ The period to compute the rate of change of the smoothed range.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addChaikinVolatility
{
my ($self, $height, $period1, $period2, $color) = @_;
my $label = "Chaikin Volatility ($period1, $period2)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_highData'})->sub(
$self->{'m_lowData'})->expAvg(2.0 / ($period1 + 1))->rate($period2)->sub(1)->mul(100
)->result(), $color, $label);
}
#/
#/ Add a Close Location Value indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addCLV
{
my ($self, $height, $color) = @_;
#Close Location Value = ((C - L) - (H - C)) / (H - L)
my $range = new ArrayMath($self->{'m_highData'})->sub($self->{'m_lowData'})->result();
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->mul(2)->sub(
$self->{'m_lowData'})->sub($self->{'m_highData'})->financeDiv($range, 0)->result(), $color,
"Close Location Value");
}
#/
#/ Add a Detrended Price Oscillator indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addDPO
{
my ($self, $height, $period, $color) = @_;
my $label = "DPO ($period)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->movAvg($period
)->shift($period / 2 + 1)->sub($self->{'m_closeData'})->mul(-1)->result(), $color, $label);
}
#/
#/ Add a Donchian Channel Width indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addDonchianWidth
{
my ($self, $height, $period, $color) = @_;
my $label = "Donchian Width ($period)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_highData'})->movMax($period
)->sub(new ArrayMath($self->{'m_lowData'})->movMin($period)->result())->result(), $color,
$label);
}
#/
#/ Add a Ease of Movement indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to smooth the indicator.
#/ The color of the indicator line.
#/ The color of the smoothed indicator line.
#/ The XYChart object representing the chart created.
sub addEaseOfMovement
{
my ($self, $height, $period, $color1, $color2) = @_;
my $boxRatioInverted = new ArrayMath($self->{'m_highData'})->sub($self->{'m_lowData'}
)->financeDiv($self->{'m_volData'}, 0)->result();
my $result = new ArrayMath($self->{'m_highData'})->add($self->{'m_lowData'})->div(2)->delta(
)->mul($boxRatioInverted)->result();
my $c = $self->addLineIndicator($height, $result, $color1, "EMV");
my $label = "EMV EMA ($period)";
$self->addLineIndicator2($c, new ArrayMath($result)->movAvg($period)->result(), $color2, $label)
;
return $c;
}
#/
#/ Add a Fast Stochastic indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the %K line.
#/ The period to compute the %D line.
#/ The color of the %K line.
#/ The color of the %D line.
#/ The XYChart object representing the chart created.
sub addFastStochastic
{
my ($self, $height, $period1, $period2, $color1, $color2) = @_;
my $movLow = new ArrayMath($self->{'m_lowData'})->movMin($period1)->result();
my $movRange = new ArrayMath($self->{'m_highData'})->movMax($period1)->sub($movLow)->result();
my $stochastic = new ArrayMath($self->{'m_closeData'})->sub($movLow)->financeDiv($movRange, 0.5
)->mul(100)->result();
my $label1 = "Fast Stochastic %K ($period1)";
my $c = $self->addLineIndicator($height, $stochastic, $color1, $label1);
my $label2 = "%D ($period2)";
$self->addLineIndicator2($c, new ArrayMath($stochastic)->movAvg($period2)->result(), $color2,
$label2);
$c->yAxis()->setLinearScale(0, 100);
return $c;
}
#/
#/ Add a MACD indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The first moving average period to compute the indicator.
#/ The second moving average period to compute the indicator.
#/ The moving average period of the signal line.
#/ The color of the indicator line.
#/ The color of the signal line.
#/ The color of the divergent bars.
#/ The XYChart object representing the chart created.
sub addMACD
{
my ($self, $height, $period1, $period2, $period3, $color, $signalColor, $divColor) = @_;
my $c = $self->addIndicator($height);
#MACD is defined as the difference between two exponential averages (typically 12/26 days)
my $expAvg1 = new ArrayMath($self->{'m_closeData'})->expAvg(2.0 / ($period1 + 1))->result();
my $macd = new ArrayMath($self->{'m_closeData'})->expAvg(2.0 / ($period2 + 1))->sub($expAvg1
)->result();
#Add the MACD line
my $label1 = "MACD ($period1, $period2)";
$self->addLineIndicator2($c, $macd, $color, $label1);
#MACD signal line
my $macdSignal = new ArrayMath($macd)->expAvg(2.0 / ($period3 + 1))->result();
my $label2 = "EXP ($period3)";
$self->addLineIndicator2($c, $macdSignal, $signalColor, $label2);
#Divergence
$self->addBarIndicator2($c, new ArrayMath($macd)->sub($macdSignal)->result(), $divColor,
"Divergence");
return $c;
}
#/
#/ Add a Mass Index indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color of the indicator line.
#/ The fill color when the indicator exceeds the upper threshold line.
#/ The fill color when the indicator falls below the lower threshold line.
#/ The XYChart object representing the chart created.
sub addMassIndex
{
my ($self, $height, $color, $upColor, $downColor) = @_;
#Mass Index
my $f = 2.0 / (10);
my $exp9 = new ArrayMath($self->{'m_highData'})->sub($self->{'m_lowData'})->expAvg($f)->result()
;
my $exp99 = new ArrayMath($exp9)->expAvg($f)->result();
my $c = $self->addLineIndicator($height, new ArrayMath($exp9)->financeDiv($exp99, 1)->movAvg(25
)->mul(25)->result(), $color, "Mass Index");
$c->yAxis()->addMark(27, $upColor);
$c->yAxis()->addMark(26.5, $downColor);
return $c;
}
#/
#/ Add a Money Flow Index indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The distance beween the middle line and the upper and lower threshold lines.
#/ The fill color when the indicator exceeds the upper threshold line.
#/ The fill color when the indicator falls below the lower threshold line.
#/ The XYChart object representing the chart created.
sub addMFI
{
my ($self, $height, $period, $color, $range, $upColor, $downColor) = @_;
#Money Flow Index
my $typicalPrice = new ArrayMath($self->{'m_highData'})->add($self->{'m_lowData'})->add(
$self->{'m_closeData'})->div(3)->result();
my $moneyFlow = new ArrayMath($typicalPrice)->mul($self->{'m_volData'})->result();
my $selector = new ArrayMath($typicalPrice)->delta()->result();
my $posMoneyFlow = new ArrayMath($moneyFlow)->selectGTZ($selector)->movAvg($period)->result();
my $posNegMoneyFlow = new ArrayMath($moneyFlow)->selectLTZ($selector)->movAvg($period)->add(
$posMoneyFlow)->result();
my $c = $self->addIndicator($height);
my $label = "Money Flow Index ($period)";
my $layer = $self->addLineIndicator2($c, new ArrayMath($posMoneyFlow)->financeDiv(
$posNegMoneyFlow, 0.5)->mul(100)->result(), $color, $label);
$self->addThreshold($c, $layer, 50 + $range, $upColor, 50 - $range, $downColor);
$c->yAxis()->setLinearScale(0, 100);
return $c;
}
#/
#/ Add a Momentum indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addMomentum
{
my ($self, $height, $period, $color) = @_;
my $label = "Momentum ($period)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->delta($period
)->result(), $color, $label);
}
#/
#/ Add a Negative Volume Index indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the signal line.
#/ The color of the indicator line.
#/ The color of the signal line.
#/ The XYChart object representing the chart created.
sub addNVI
{
my ($self, $height, $period, $color, $signalColor) = @_;
my $nvi = [(0) x scalar(@{$self->{'m_volData'}})];
my $previousNVI = 100;
my $previousVol = $perlchartdir::NoValue;
my $previousClose = $perlchartdir::NoValue;
for(my $i = 0; $i < scalar(@{$self->{'m_volData'}}); ++$i) {
if ($self->{'m_volData'}->[$i] == $perlchartdir::NoValue) {
$nvi->[$i] = $perlchartdir::NoValue;
} else {
if (($previousVol != $perlchartdir::NoValue) && ($self->{'m_volData'}->[$i] <
$previousVol) && ($previousClose != $perlchartdir::NoValue) && (
$self->{'m_closeData'}->[$i] != $perlchartdir::NoValue)) {
$nvi->[$i] = $previousNVI + $previousNVI * ($self->{'m_closeData'}->[$i] -
$previousClose) / $previousClose;
} else {
$nvi->[$i] = $previousNVI;
}
$previousNVI = $nvi->[$i];
$previousVol = $self->{'m_volData'}->[$i];
$previousClose = $self->{'m_closeData'}->[$i];
}
}
my $c = $self->addLineIndicator($height, $nvi, $color, "NVI");
if (scalar(@$nvi) > $period) {
my $label = "NVI SMA ($period)";
$self->addLineIndicator2($c, new ArrayMath($nvi)->movAvg($period)->result(), $signalColor,
$label);
}
return $c;
}
#/
#/ Add an On Balance Volume indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addOBV
{
my ($self, $height, $color) = @_;
my $closeChange = new ArrayMath($self->{'m_closeData'})->delta()->result();
my $upVolume = new ArrayMath($self->{'m_volData'})->selectGTZ($closeChange)->result();
my $downVolume = new ArrayMath($self->{'m_volData'})->selectLTZ($closeChange)->result();
return $self->addLineIndicator($height, new ArrayMath($upVolume)->sub($downVolume)->acc(
)->result(), $color, "OBV");
}
#/
#/ Add a Performance indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addPerformance
{
my ($self, $height, $color) = @_;
my $closeValue = $self->firstCloseValue();
if ($closeValue != $perlchartdir::NoValue) {
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->mul(100 /
$closeValue)->sub(100)->result(), $color, "Performance");
} else {
#chart is empty !!!
return $self->addIndicator($height);
}
}
#/
#/ Add a Percentage Price Oscillator indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The first moving average period to compute the indicator.
#/ The second moving average period to compute the indicator.
#/ The moving average period of the signal line.
#/ The color of the indicator line.
#/ The color of the signal line.
#/ The color of the divergent bars.
#/ The XYChart object representing the chart created.
sub addPPO
{
my ($self, $height, $period1, $period2, $period3, $color, $signalColor, $divColor) = @_;
my $expAvg1 = new ArrayMath($self->{'m_closeData'})->expAvg(2.0 / ($period1 + 1))->result();
my $expAvg2 = new ArrayMath($self->{'m_closeData'})->expAvg(2.0 / ($period2 + 1))->result();
my $ppo = new ArrayMath($expAvg2)->sub($expAvg1)->financeDiv($expAvg2, 0)->mul(100);
my $ppoSignal = new ArrayMath($ppo->result())->expAvg(2.0 / ($period3 + 1))->result();
my $label1 = "PPO ($period1, $period2)";
my $label2 = "EMA ($period3)";
my $c = $self->addLineIndicator($height, $ppo->result(), $color, $label1);
$self->addLineIndicator2($c, $ppoSignal, $signalColor, $label2);
$self->addBarIndicator2($c, $ppo->sub($ppoSignal)->result(), $divColor, "Divergence");
return $c;
}
#/
#/ Add a Positive Volume Index indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the signal line.
#/ The color of the indicator line.
#/ The color of the signal line.
#/ The XYChart object representing the chart created.
sub addPVI
{
my ($self, $height, $period, $color, $signalColor) = @_;
#Positive Volume Index
my $pvi = [(0) x scalar(@{$self->{'m_volData'}})];
my $previousPVI = 100;
my $previousVol = $perlchartdir::NoValue;
my $previousClose = $perlchartdir::NoValue;
for(my $i = 0; $i < scalar(@{$self->{'m_volData'}}); ++$i) {
if ($self->{'m_volData'}->[$i] == $perlchartdir::NoValue) {
$pvi->[$i] = $perlchartdir::NoValue;
} else {
if (($previousVol != $perlchartdir::NoValue) && ($self->{'m_volData'}->[$i] >
$previousVol) && ($previousClose != $perlchartdir::NoValue) && (
$self->{'m_closeData'}->[$i] != $perlchartdir::NoValue)) {
$pvi->[$i] = $previousPVI + $previousPVI * ($self->{'m_closeData'}->[$i] -
$previousClose) / $previousClose;
} else {
$pvi->[$i] = $previousPVI;
}
$previousPVI = $pvi->[$i];
$previousVol = $self->{'m_volData'}->[$i];
$previousClose = $self->{'m_closeData'}->[$i];
}
}
my $c = $self->addLineIndicator($height, $pvi, $color, "PVI");
if (scalar(@$pvi) > $period) {
my $label = "PVI SMA ($period)";
$self->addLineIndicator2($c, new ArrayMath($pvi)->movAvg($period)->result(), $signalColor,
$label);
}
return $c;
}
#/
#/ Add a Percentage Volume Oscillator indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The first moving average period to compute the indicator.
#/ The second moving average period to compute the indicator.
#/ The moving average period of the signal line.
#/ The color of the indicator line.
#/ The color of the signal line.
#/ The color of the divergent bars.
#/ The XYChart object representing the chart created.
sub addPVO
{
my ($self, $height, $period1, $period2, $period3, $color, $signalColor, $divColor) = @_;
my $expAvg1 = new ArrayMath($self->{'m_volData'})->expAvg(2.0 / ($period1 + 1))->result();
my $expAvg2 = new ArrayMath($self->{'m_volData'})->expAvg(2.0 / ($period2 + 1))->result();
my $pvo = new ArrayMath($expAvg2)->sub($expAvg1)->financeDiv($expAvg2, 0)->mul(100);
my $pvoSignal = new ArrayMath($pvo->result())->expAvg(2.0 / ($period3 + 1))->result();
my $label1 = "PVO ($period1, $period2)";
my $label2 = "EMA ($period3)";
my $c = $self->addLineIndicator($height, $pvo->result(), $color, $label1);
$self->addLineIndicator2($c, $pvoSignal, $signalColor, $label2);
$self->addBarIndicator2($c, $pvo->sub($pvoSignal)->result(), $divColor, "Divergence");
return $c;
}
#/
#/ Add a Price Volumne Trend indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addPVT
{
my ($self, $height, $color) = @_;
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->rate()->sub(1
)->mul($self->{'m_volData'})->acc()->result(), $color, "PVT");
}
#/
#/ Add a Rate of Change indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addROC
{
my ($self, $height, $period, $color) = @_;
my $label = "ROC ($period)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->rate($period
)->sub(1)->mul(100)->result(), $color, $label);
}
sub RSIMovAvg
{
my ($self, $data, $period) = @_;
#The "moving average" in classical RSI is based on a formula that mixes simple
#and exponential moving averages.
if ($period <= 0) {
$period = 1;
}
my $count = 0;
my $acc = 0;
for(my $i = 0; $i < scalar(@$data); ++$i) {
if (abs($data->[$i] / $perlchartdir::NoValue - 1) > 1e-5) {
$count = $count + 1;
$acc = $acc + $data->[$i];
if ($count < $period) {
$data->[$i] = $perlchartdir::NoValue;
} else {
$data->[$i] = $acc / $period;
$acc = $data->[$i] * ($period - 1);
}
}
}
return $data;
}
sub computeRSI
{
my ($self, $period) = @_;
#RSI is defined as the average up changes for the last 14 days, divided by the
#average absolute changes for the last 14 days, expressed as a percentage.
my $absChange = $self->RSIMovAvg(new ArrayMath($self->{'m_closeData'})->delta()->abs()->result(
), $period);
my $absUpChange = $self->RSIMovAvg(new ArrayMath($self->{'m_closeData'})->delta()->selectGTZ(
)->result(), $period);
return new ArrayMath($absUpChange)->financeDiv($absChange, 0.5)->mul(100)->result();
}
#/
#/ Add a Relative Strength Index indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The distance beween the middle line and the upper and lower threshold lines.
#/ The fill color when the indicator exceeds the upper threshold line.
#/ The fill color when the indicator falls below the lower threshold line.
#/ The XYChart object representing the chart created.
sub addRSI
{
my ($self, $height, $period, $color, $range, $upColor, $downColor) = @_;
my $c = $self->addIndicator($height);
my $label = "RSI ($period)";
my $layer = $self->addLineIndicator2($c, $self->computeRSI($period), $color, $label);
#Add range if given
if (($range > 0) && ($range < 50)) {
$self->addThreshold($c, $layer, 50 + $range, $upColor, 50 - $range, $downColor);
}
$c->yAxis()->setLinearScale(0, 100);
return $c;
}
#/
#/ Add a Slow Stochastic indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the %K line.
#/ The period to compute the %D line.
#/ The color of the %K line.
#/ The color of the %D line.
#/ The XYChart object representing the chart created.
sub addSlowStochastic
{
my ($self, $height, $period1, $period2, $color1, $color2) = @_;
my $movLow = new ArrayMath($self->{'m_lowData'})->movMin($period1)->result();
my $movRange = new ArrayMath($self->{'m_highData'})->movMax($period1)->sub($movLow)->result();
my $stochastic = new ArrayMath($self->{'m_closeData'})->sub($movLow)->financeDiv($movRange, 0.5
)->mul(100)->movAvg(3);
my $label1 = "Slow Stochastic %K ($period1)";
my $label2 = "%D ($period2)";
my $c = $self->addLineIndicator($height, $stochastic->result(), $color1, $label1);
$self->addLineIndicator2($c, $stochastic->movAvg($period2)->result(), $color2, $label2);
$c->yAxis()->setLinearScale(0, 100);
return $c;
}
#/
#/ Add a Moving Standard Deviation indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addStdDev
{
my ($self, $height, $period, $color) = @_;
my $label = "Moving StdDev ($period)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->movStdDev($period
)->result(), $color, $label);
}
#/
#/ Add a Stochastic RSI indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The distance beween the middle line and the upper and lower threshold lines.
#/ The fill color when the indicator exceeds the upper threshold line.
#/ The fill color when the indicator falls below the lower threshold line.
#/ The XYChart object representing the chart created.
sub addStochRSI
{
my ($self, $height, $period, $color, $range, $upColor, $downColor) = @_;
my $rsi = $self->computeRSI($period);
my $movLow = new ArrayMath($rsi)->movMin($period)->result();
my $movRange = new ArrayMath($rsi)->movMax($period)->sub($movLow)->result();
my $c = $self->addIndicator($height);
my $label = "StochRSI ($period)";
my $layer = $self->addLineIndicator2($c, new ArrayMath($rsi)->sub($movLow)->financeDiv(
$movRange, 0.5)->mul(100)->result(), $color, $label);
#Add range if given
if (($range > 0) && ($range < 50)) {
$self->addThreshold($c, $layer, 50 + $range, $upColor, 50 - $range, $downColor);
}
$c->yAxis()->setLinearScale(0, 100);
return $c;
}
#/
#/ Add a TRIX indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The XYChart object representing the chart created.
sub addTRIX
{
my ($self, $height, $period, $color) = @_;
my $f = 2.0 / ($period + 1);
my $label = "TRIX ($period)";
return $self->addLineIndicator($height, new ArrayMath($self->{'m_closeData'})->expAvg($f
)->expAvg($f)->expAvg($f)->rate()->sub(1)->mul(100)->result(), $color, $label);
}
sub computeTrueLow
{
my ($self) = @_;
#the lower of today's low or yesterday's close.
my $previousClose = new ArrayMath($self->{'m_closeData'})->shift()->result();
my $ret = [(0) x scalar(@{$self->{'m_lowData'}})];
for(my $i = 0; $i < scalar(@{$self->{'m_lowData'}}); ++$i) {
if (($self->{'m_lowData'}->[$i] != $perlchartdir::NoValue) && ($previousClose->[$i] !=
$perlchartdir::NoValue)) {
if ($self->{'m_lowData'}->[$i] < $previousClose->[$i]) {
$ret->[$i] = $self->{'m_lowData'}->[$i];
} else {
$ret->[$i] = $previousClose->[$i];
}
} else {
$ret->[$i] = $perlchartdir::NoValue;
}
}
return $ret;
}
#/
#/ Add an Ultimate Oscillator indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The first moving average period to compute the indicator.
#/ The second moving average period to compute the indicator.
#/ The third moving average period to compute the indicator.
#/ The color of the indicator line.
#/ The distance beween the middle line and the upper and lower threshold lines.
#/ The fill color when the indicator exceeds the upper threshold line.
#/ The fill color when the indicator falls below the lower threshold line.
#/ The XYChart object representing the chart created.
sub addUltimateOscillator
{
my ($self, $height, $period1, $period2, $period3, $color, $range, $upColor, $downColor) = @_;
my $trueLow = $self->computeTrueLow();
my $buyingPressure = new ArrayMath($self->{'m_closeData'})->sub($trueLow)->result();
my $trueRange = $self->computeTrueRange();
my $rawUO1 = new ArrayMath($buyingPressure)->movAvg($period1)->financeDiv(new ArrayMath(
$trueRange)->movAvg($period1)->result(), 0.5)->mul(4)->result();
my $rawUO2 = new ArrayMath($buyingPressure)->movAvg($period2)->financeDiv(new ArrayMath(
$trueRange)->movAvg($period2)->result(), 0.5)->mul(2)->result();
my $rawUO3 = new ArrayMath($buyingPressure)->movAvg($period3)->financeDiv(new ArrayMath(
$trueRange)->movAvg($period3)->result(), 0.5)->mul(1)->result();
my $c = $self->addIndicator($height);
my $label = "Ultimate Oscillator ($period1, $period2, $period3)";
my $layer = $self->addLineIndicator2($c, new ArrayMath($rawUO1)->add($rawUO2)->add($rawUO3
)->mul(100.0 / 7)->result(), $color, $label);
$self->addThreshold($c, $layer, 50 + $range, $upColor, 50 - $range, $downColor);
$c->yAxis()->setLinearScale(0, 100);
return $c;
}
#/
#/ Add a Volume indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The color to used on an 'up' day. An 'up' day is a day where
#/ the closing price is higher than that of the previous day.
#/ The color to used on a 'down' day. A 'down' day is a day
#/ where the closing price is lower than that of the previous day.
#/ The color to used on a 'flat' day. A 'flat' day is a day
#/ where the closing price is the same as that of the previous day.
#/ The XYChart object representing the chart created.
sub addVolIndicator
{
my ($self, $height, $upColor, $downColor, $flatColor) = @_;
my $c = $self->addIndicator($height);
$self->addVolBars2($c, $height, $upColor, $downColor, $flatColor);
return $c;
}
#/
#/ Add a William %R indicator chart.
#/
#/ The height of the indicator chart in pixels.
#/ The period to compute the indicator.
#/ The color of the indicator line.
#/ The distance beween the middle line and the upper and lower threshold lines.
#/ The fill color when the indicator exceeds the upper threshold line.
#/ The fill color when the indicator falls below the lower threshold line.
#/ The XYChart object representing the chart created.
sub addWilliamR
{
my ($self, $height, $period, $color, $range, $upColor, $downColor) = @_;
my $movLow = new ArrayMath($self->{'m_lowData'})->movMin($period)->result();
my $movHigh = new ArrayMath($self->{'m_highData'})->movMax($period)->result();
my $movRange = new ArrayMath($movHigh)->sub($movLow)->result();
my $c = $self->addIndicator($height);
my $layer = $self->addLineIndicator2($c, new ArrayMath($movHigh)->sub($self->{'m_closeData'}
)->financeDiv($movRange, 0.5)->mul(-100)->result(), $color, "William %R");
$self->addThreshold($c, $layer, -50 + $range, $upColor, -50 - $range, $downColor);
$c->yAxis()->setLinearScale(-100, 0);
return $c;
}
1;