using ChartDirector; using System; namespace Custom { /////////////////////////////////////////////////////////////////////////////////////////////////// // 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 /// public class FinanceChart : MultiChart { int m_totalWidth = 0; int m_totalHeight = 0; bool m_antiAlias = true; bool m_logScale = false; bool m_axisOnRight = true; int m_leftMargin = 40; int m_rightMargin = 40; int m_topMargin = 30; int m_bottomMargin = 30; int m_plotAreaBgColor = 0xffffff; int m_plotAreaBorder = 0x888888; int m_plotAreaGap = 2; int m_majorHGridColor = 0xdddddd; int m_minorHGridColor = 0xdddddd; int m_majorVGridColor = 0xdddddd; int m_minorVGridColor = 0xdddddd; string m_legendFont = "normal"; double m_legendFontSize = 8; int m_legendFontColor = Chart.TextColor; int m_legendBgColor = unchecked((int)0x80cccccc); string m_yAxisFont = "normal"; double m_yAxisFontSize = 8; int m_yAxisFontColor = Chart.TextColor; int m_yAxisMargin = 14; string m_xAxisFont = "normal"; double m_xAxisFontSize = 8; int m_xAxisFontColor = Chart.TextColor; double m_xAxisFontAngle = 0; double[] m_timeStamps = null; double[] m_highData = null; double[] m_lowData = null; double[] m_openData = null; double[] m_closeData = null; double[] m_volData = null; string m_volUnit = ""; int m_extraPoints = 0; string m_yearFormat = "{value|yyyy}"; string m_firstMonthFormat = "<*font=bold*>{value|mmm yy}"; string m_otherMonthFormat = "{value|mmm}"; string m_firstDayFormat = "<*font=bold*>{value|d mmm}"; string m_otherDayFormat = "{value|d}"; string m_firstHourFormat = "<*font=bold*>{value|d mmm\nh:nna}"; string m_otherHourFormat = "{value|h:nna}"; int m_timeLabelSpacing = 50; string m_generalFormat = "P3"; string m_toolTipMonthFormat = "[{xLabel|mmm yyyy}]"; string m_toolTipDayFormat = "[{xLabel|mmm d, yyyy}]"; string m_toolTipHourFormat = "[{xLabel|mmm d, yyyy hh:nn:ss}]"; XYChart m_mainChart = null; XYChart m_currentChart = null; /// /// Create a FinanceChart with a given width. The height will be automatically determined /// as the chart is built. /// /// Width of the chart in pixels public FinanceChart(int width) : base(width, 1) { m_totalWidth = width; } /// /// 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. public void enableAntiAlias(bool antiAlias) { 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. public void setMargins(int leftMargin, int topMargin, int rightMargin, int bottomMargin) { m_leftMargin = leftMargin; m_rightMargin = rightMargin; m_topMargin = topMargin; 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. public ChartDirector.TextBox addPlotAreaTitle(int alignment, string text) { ChartDirector.TextBox ret = addText(m_leftMargin, 0, text, "bold", 10, Chart.TextColor, alignment); ret.setSize(m_totalWidth - m_leftMargin - m_rightMargin + 1, 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. public void setPlotAreaStyle(int bgColor, int majorHGridColor, int majorVGridColor, int minorHGridColor, int minorVGridColor) { m_plotAreaBgColor = bgColor; m_majorHGridColor = majorHGridColor; m_majorVGridColor = majorVGridColor; m_minorHGridColor = minorHGridColor; 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. public void setPlotAreaBorder(int borderColor, int borderGap) { m_plotAreaBorder = borderColor; 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. public void setLegendStyle(string font, double fontSize, int fontColor, int bgColor) { m_legendFont = font; m_legendFontSize = fontSize; m_legendFontColor = fontColor; 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. public void setXAxisStyle(string font, double fontSize, int fontColor, double fontAngle) { m_xAxisFont = font; m_xAxisFontSize = fontSize; m_xAxisFontColor = fontColor; 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). public void setYAxisStyle(string font, double fontSize, int fontColor, int axisMargin) { m_yAxisFont = font; m_yAxisFontSize = fontSize; m_yAxisFontColor = fontColor; 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. public void setAxisOnRight(bool b) { 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. public void setLogScale(bool b) { m_logScale = b; if (m_mainChart != null) { if (m_logScale) { m_mainChart.yAxis().setLogScale(); } else { 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. public void setDateLabelFormat(string yearFormat, string firstMonthFormat, string otherMonthFormat, string firstDayFormat, string otherDayFormat, string firstHourFormat, string otherHourFormat) { if (yearFormat != null) { m_yearFormat = yearFormat; } if (firstMonthFormat != null) { m_firstMonthFormat = firstMonthFormat; } if (otherMonthFormat != null) { m_otherMonthFormat = otherMonthFormat; } if (firstDayFormat != null) { m_firstDayFormat = firstDayFormat; } if (otherDayFormat != null) { m_otherDayFormat = otherDayFormat; } if (firstHourFormat != null) { m_firstHourFormat = firstHourFormat; } if (otherHourFormat != null) { m_otherHourFormat = otherHourFormat; } } /// /// This method is for backward compatibility - use setDataLabelFormat instead. /// public void setTimeLabelFormats(string yearFormat, string firstMonthFormat, string otherMonthFormat, string firstDayFormat, string otherDayFormat, string firstHourFormat, string otherHourFormat) { setDateLabelFormat(yearFormat, firstMonthFormat, otherMonthFormat, firstDayFormat, otherDayFormat, firstHourFormat, otherHourFormat); } /// /// Set the minimum label spacing between two labels on the time axis /// /// The minimum label spacing in pixels. public void setDateLabelSpacing(int labelSpacing) { if (labelSpacing > 0) { m_timeLabelSpacing = labelSpacing; } else { m_timeLabelSpacing = 0; } } /// /// This function is for backward compatibility. It has no purpose. /// public void enableToolTips(bool b, string dateTimeFormat) { //do nothing } /// /// 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. public void setToolTipDateFormat(string monthFormat, string dayFormat, string hourFormat) { if (monthFormat != null) { m_toolTipMonthFormat = monthFormat; } if (dayFormat != null) { m_toolTipDayFormat = dayFormat; } if (hourFormat != null) { m_toolTipHourFormat = hourFormat; } } /// /// Get the tool tip format for display date/time /// /// The tool tip format string. public string getToolTipDateFormat() { if (m_timeStamps == null) { return m_toolTipHourFormat; } if (m_timeStamps.Length <= m_extraPoints) { return m_toolTipHourFormat; } double resolution = (m_timeStamps[m_timeStamps.Length - 1] - m_timeStamps[0]) / ( m_timeStamps.Length); if (resolution >= 30 * 86400) { return m_toolTipMonthFormat; } else if (resolution >= 86400) { return m_toolTipDayFormat; } else { return m_toolTipHourFormat; } } /// /// Set the number format for use in displaying values in legend keys and tool tips. /// /// The default number format. public void setNumberLabelFormat(string formatString) { if (formatString != null) { 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. private double[] computeTriMovingAvg(double[] data, int period) { int 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. private double[] computeWeightedMovingAvg(double[] data, int period) { ArrayMath acc = new ArrayMath(data); for(int 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. private double firstCloseValue() { for(int i = m_extraPoints; i < m_closeData.Length; ++i) { if ((m_closeData[i] != Chart.NoValue) && (m_closeData[i] != 0)) { return m_closeData[i]; } } return Chart.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. private int lastIndex(double[] data) { int i = data.Length - 1; while (i >= 0) { if (data[i] != Chart.NoValue) { break; } 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. public void setData(DateTime[] timeStamps, double[] highData, double[] lowData, double[] openData, double[] closeData, double[] volData, int extraPoints) { setData(Chart.CTime(timeStamps), highData, lowData, openData, closeData, volData, extraPoints); } /// /// 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. public void setData(double[] timeStamps, double[] highData, double[] lowData, double[] openData, double[] closeData, double[] volData, int extraPoints) { m_timeStamps = timeStamps; m_highData = highData; m_lowData = lowData; m_openData = openData; m_closeData = closeData; if (extraPoints > 0) { m_extraPoints = extraPoints; } else { m_extraPoints = 0; } ///////////////////////////////////////////////////////////////////////// // Auto-detect volume units ///////////////////////////////////////////////////////////////////////// double maxVol = new ArrayMath(volData).max(); string[] units = {"", "K", "M", "B"}; int unitIndex = units.Length - 1; while ((unitIndex > 0) && (maxVol < Math.Pow(1000, unitIndex))) { unitIndex = unitIndex - 1; } m_volData = new ArrayMath(volData).div(Math.Pow(1000, unitIndex)).result(); m_volUnit = units[unitIndex]; } ////////////////////////////////////////////////////////////////////////////// // Format x-axis labels ////////////////////////////////////////////////////////////////////////////// private void setXLabels(Axis a) { a.setLabels2(m_timeStamps); if (m_extraPoints < m_timeStamps.Length) { int tickStep = (int)((m_timeStamps.Length - m_extraPoints) * m_timeLabelSpacing / ( m_totalWidth - m_leftMargin - m_rightMargin)) + 1; double timeRangeInSeconds = m_timeStamps[m_timeStamps.Length - 1] - m_timeStamps[ m_extraPoints]; double secondsBetweenTicks = timeRangeInSeconds / (m_totalWidth - m_leftMargin - m_rightMargin) * m_timeLabelSpacing; if (secondsBetweenTicks * (m_timeStamps.Length - m_extraPoints) <= timeRangeInSeconds) { tickStep = 1; if (m_timeStamps.Length > 1) { secondsBetweenTicks = m_timeStamps[m_timeStamps.Length - 1] - m_timeStamps[ m_timeStamps.Length - 2]; } else { secondsBetweenTicks = 86400; } } if ((secondsBetweenTicks > 360 * 86400) || ((secondsBetweenTicks > 90 * 86400) && ( timeRangeInSeconds >= 720 * 86400))) { //yearly ticks a.setMultiFormat2(Chart.StartOfYearFilter(), m_yearFormat, tickStep); } else if ((secondsBetweenTicks >= 30 * 86400) || ((secondsBetweenTicks > 7 * 86400) && (timeRangeInSeconds >= 60 * 86400))) { //monthly ticks int monthBetweenTicks = (int)(secondsBetweenTicks / 31 / 86400) + 1; a.setMultiFormat(Chart.StartOfYearFilter(), m_firstMonthFormat, Chart.StartOfMonthFilter(monthBetweenTicks), m_otherMonthFormat); a.setMultiFormat2(Chart.StartOfMonthFilter(), "-", 1, false); } else if ((secondsBetweenTicks >= 86400) || ((secondsBetweenTicks > 6 * 3600) && ( timeRangeInSeconds >= 86400))) { //daily ticks a.setMultiFormat(Chart.StartOfMonthFilter(), m_firstDayFormat, Chart.StartOfDayFilter(1, 0.5), m_otherDayFormat, tickStep); } else { //hourly ticks a.setMultiFormat(Chart.StartOfDayFilter(1, 0.5), m_firstHourFormat, Chart.StartOfHourFilter(1, 0.5), m_otherHourFormat, tickStep); } } } ////////////////////////////////////////////////////////////////////////////// // Create tool tip format string for showing OHLC data ////////////////////////////////////////////////////////////////////////////// private string getHLOCToolTipFormat() { return "title='" + getToolTipDateFormat() + " Op:{open|" + m_generalFormat + "}, Hi:{high|" + m_generalFormat + "}, Lo:{low|" + m_generalFormat + "}, Cl:{close|" + 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. public XYChart addMainChart(int height) { m_mainChart = addIndicator(height); setMainChart(m_mainChart); m_mainChart.yAxis().setMargin(2 * m_yAxisMargin); if (m_logScale) { m_mainChart.yAxis().setLogScale(); } else { m_mainChart.yAxis().setLinearScale(); } return 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. public CandleStickLayer addCandleStick(int upColor, int downColor) { addOHLCLabel(upColor, downColor, true); CandleStickLayer ret = m_mainChart.addCandleStickLayer(m_highData, m_lowData, m_openData, m_closeData, upColor, downColor); ret.setHTMLImageMap("", "", getHLOCToolTipFormat()); if (m_highData.Length - m_extraPoints > 60) { ret.setDataGap(0); } if (m_highData.Length > m_extraPoints) { int expectedWidth = (m_totalWidth - m_leftMargin - m_rightMargin) / ( m_highData.Length - 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. /// The HLOCLayer created. public HLOCLayer addHLOC(int color) { return addHLOC(color, color); } /// /// 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. public HLOCLayer addHLOC(int upColor, int downColor) { addOHLCLabel(upColor, downColor, false); HLOCLayer ret = m_mainChart.addHLOCLayer(m_highData, m_lowData, m_openData, m_closeData) ; ret.setColorMethod(Chart.HLOCUpDown, upColor, downColor); ret.setHTMLImageMap("", "", getHLOCToolTipFormat()); ret.setDataGap(0); return ret; } private void addOHLCLabel(int upColor, int downColor, bool candleStickMode) { int i = lastIndex(m_closeData); if (i >= 0) { double openValue = Chart.NoValue; double closeValue = Chart.NoValue; double highValue = Chart.NoValue; double lowValue = Chart.NoValue; if (i < m_openData.Length) { openValue = m_openData[i]; } if (i < m_closeData.Length) { closeValue = m_closeData[i]; } if (i < m_highData.Length) { highValue = m_highData[i]; } if (i < m_lowData.Length) { lowValue = m_lowData[i]; } string openLabel = ""; string closeLabel = ""; string highLabel = ""; string lowLabel = ""; string delim = ""; if (openValue != Chart.NoValue) { openLabel = "Op:" + formatValue(openValue, m_generalFormat); delim = ", "; } if (highValue != Chart.NoValue) { highLabel = delim + "Hi:" + formatValue(highValue, m_generalFormat); delim = ", "; } if (lowValue != Chart.NoValue) { lowLabel = delim + "Lo:" + formatValue(lowValue, m_generalFormat); delim = ", "; } if (closeValue != Chart.NoValue) { closeLabel = delim + "Cl:" + formatValue(closeValue, m_generalFormat); delim = ", "; } string label = openLabel + highLabel + lowLabel + closeLabel; bool useUpColor = (closeValue >= openValue); if (candleStickMode != true) { double[] closeChanges = new ArrayMath(m_closeData).delta().result(); int lastChangeIndex = lastIndex(closeChanges); useUpColor = (lastChangeIndex < 0); if (useUpColor != true) { useUpColor = (closeChanges[lastChangeIndex] >= 0); } } int udcolor = downColor; if (useUpColor) { udcolor = upColor; } 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. public LineLayer addCloseLine(int color) { return addLineIndicator2(m_mainChart, 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. public LineLayer addWeightedClose(int color) { return addLineIndicator2(m_mainChart, new ArrayMath(m_highData).add(m_lowData).add( m_closeData).add(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. public LineLayer addTypicalPrice(int color) { return addLineIndicator2(m_mainChart, new ArrayMath(m_highData).add(m_lowData).add( 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. public LineLayer addMedianPrice(int color) { return addLineIndicator2(m_mainChart, new ArrayMath(m_highData).add(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. public LineLayer addSimpleMovingAvg(int period, int color) { string label = "SMA (" + period + ")"; return addLineIndicator2(m_mainChart, new ArrayMath(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. public LineLayer addExpMovingAvg(int period, int color) { string label = "EMA (" + period + ")"; return addLineIndicator2(m_mainChart, new ArrayMath(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. public LineLayer addTriMovingAvg(int period, int color) { string label = "TMA (" + period + ")"; return addLineIndicator2(m_mainChart, new ArrayMath(computeTriMovingAvg(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. public LineLayer addWeightedMovingAvg(int period, int color) { string label = "WMA (" + period + ")"; return addLineIndicator2(m_mainChart, new ArrayMath(computeWeightedMovingAvg( 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. public LineLayer addParabolicSAR(double accInitial, double accIncrement, double accMaximum, int symbolType, int symbolSize, int fillColor, int edgeColor) { bool isLong = true; double acc = accInitial; double extremePoint = 0; double[] psar = new double[m_lowData.Length]; int i_1 = -1; int i_2 = -1; for(int i = 0; i < m_lowData.Length; ++i) { psar[i] = Chart.NoValue; if ((m_lowData[i] != Chart.NoValue) && (m_highData[i] != Chart.NoValue)) { if ((i_1 >= 0) && (i_2 < 0)) { if (m_lowData[i_1] <= m_lowData[i]) { psar[i] = m_lowData[i_1]; isLong = true; if (m_highData[i_1] > m_highData[i]) { extremePoint = m_highData[i_1]; } else { extremePoint = m_highData[i]; } } else { extremePoint = m_lowData[i]; isLong = false; if (m_highData[i_1] > m_highData[i]) { psar[i] = m_highData[i_1]; } else { psar[i] = m_highData[i]; } } } else if ((i_1 >= 0) && (i_2 >= 0)) { if (acc > accMaximum) { acc = accMaximum; } psar[i] = psar[i_1] + acc * (extremePoint - psar[i_1]); if (isLong) { if (m_lowData[i] < psar[i]) { isLong = false; psar[i] = extremePoint; extremePoint = m_lowData[i]; acc = accInitial; } else { if (m_highData[i] > extremePoint) { extremePoint = m_highData[i]; acc = acc + accIncrement; } if (m_lowData[i_1] < psar[i]) { psar[i] = m_lowData[i_1]; } if (m_lowData[i_2] < psar[i]) { psar[i] = m_lowData[i_2]; } } } else { if (m_highData[i] > psar[i]) { isLong = true; psar[i] = extremePoint; extremePoint = m_highData[i]; acc = accInitial; } else { if (m_lowData[i] < extremePoint) { extremePoint = m_lowData[i]; acc = acc + accIncrement; } if (m_highData[i_1] > psar[i]) { psar[i] = m_highData[i_1]; } if (m_highData[i_2] > psar[i]) { psar[i] = m_highData[i_2]; } } } } i_2 = i_1; i_1 = i; } } LineLayer ret = addLineIndicator2(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. public LineLayer addComparison(double[] data, int color, string name) { int firstIndex = m_extraPoints; while ((firstIndex < data.Length) && (firstIndex < m_closeData.Length)) { if ((data[firstIndex] != Chart.NoValue) && (m_closeData[firstIndex] != Chart.NoValue ) && (data[firstIndex] != 0) && (m_closeData[firstIndex] != 0)) { break; } firstIndex = firstIndex + 1; } if ((firstIndex >= data.Length) || (firstIndex >= m_closeData.Length)) { return null; } double scaleFactor = m_closeData[firstIndex] / data[firstIndex]; LineLayer layer = m_mainChart.addLineLayer(new ArrayMath(data).mul(scaleFactor).result( ), Chart.Transparent); layer.setHTMLImageMap("{disable}"); Axis a = m_mainChart.addAxis(Chart.Right, 0); a.setColors(Chart.Transparent, Chart.Transparent); a.syncAxis(m_mainChart.yAxis(), 1 / scaleFactor, 0); LineLayer ret = addLineIndicator2(m_mainChart, data, color, name); ret.setUseYAxis(a); return ret; } /// /// Display percentage axis scale /// /// The Axis object representing the percentage axis. public Axis setPercentageAxis() { double firstClose = firstCloseValue(); if (firstClose == Chart.NoValue) { return null; } int axisAlign = Chart.Left; if (m_axisOnRight) { axisAlign = Chart.Right; } Axis ret = m_mainChart.addAxis(axisAlign, 0); configureYAxis(ret, 300); ret.syncAxis(m_mainChart.yAxis(), 100 / firstClose); ret.setRounding(false, false); ret.setLabelFormat("{={value}-100|@}%"); m_mainChart.yAxis().setColors(Chart.Transparent, Chart.Transparent); m_mainChart.getPlotArea().setGridAxis(null, 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. public InterLineLayer addBand(double[] upperLine, double[] lowerLine, int lineColor, int fillColor, string name) { int i = upperLine.Length - 1; if (i >= lowerLine.Length) { i = lowerLine.Length - 1; } while (i >= 0) { if ((upperLine[i] != Chart.NoValue) && (lowerLine[i] != Chart.NoValue)) { name = name + ": " + formatValue(lowerLine[i], m_generalFormat) + " - " + formatValue(upperLine[i], m_generalFormat); break; } i = i - 1; } LineLayer uLayer = m_mainChart.addLineLayer(upperLine, lineColor, name); LineLayer lLayer = m_mainChart.addLineLayer(lowerLine, lineColor); return m_mainChart.addInterLineLayer(uLayer.getLine(), lLayer.getLine(), fillColor); } /// /// This method is for backward compatibility. /// - use addBand(double[], double[], int, int, string) instead. /// public InterLineLayer addBand(ArrayMath upperLine, ArrayMath lowerLine, int lineColor, int fillColor, string name) { return addBand(upperLine.result(), lowerLine.result(), lineColor, fillColor, name); } /// /// 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. public InterLineLayer addBollingerBand(int period, double bandWidth, int lineColor, int fillColor) { //Bollinger Band is moving avg +/- (width * moving std deviation) double[] stdDev = new ArrayMath(m_closeData).movStdDev(period).mul(bandWidth).result(); double[] movAvg = new ArrayMath(m_closeData).movAvg(period).result(); string label = "Bollinger (" + period + ", " + bandWidth + ")"; return addBand(new ArrayMath(movAvg).add(stdDev).result(), new ArrayMath(movAvg).sub( stdDev).selectGTZ(null, 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. public InterLineLayer addDonchianChannel(int period, int lineColor, int fillColor) { //Donchian Channel is the zone between the moving max and moving min string label = "Donchian (" + period + ")"; return addBand(new ArrayMath(m_highData).movMax(period).result(), new ArrayMath( 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. public InterLineLayer addEnvelop(int period, double range, int lineColor, int fillColor) { //Envelop is moving avg +/- percentage double[] movAvg = new ArrayMath(m_closeData).movAvg(period).result(); string label = "Envelop (SMA " + period + " +/- " + (int)(range * 100) + "%)"; return 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. public BarLayer addVolBars(int height, int upColor, int downColor, int flatColor) { return addVolBars2(m_mainChart, height, upColor, downColor, flatColor); } private BarLayer addVolBars2(XYChart c, int height, int upColor, int downColor, int flatColor) { BarLayer barLayer = c.addBarLayer2(Chart.Overlay); barLayer.setBorderColor(Chart.Transparent); if (c == m_mainChart) { configureYAxis(c.yAxis2(), height); int topMargin = c.getDrawArea().getHeight() - m_topMargin - m_bottomMargin - height + m_yAxisMargin; if (topMargin < 0) { topMargin = 0; } c.yAxis2().setTopMargin(topMargin); barLayer.setUseYAxis2(); } Axis a = c.yAxis2(); if (c != m_mainChart) { a = c.yAxis(); } if (new ArrayMath(m_volData).max() < 10) { a.setLabelFormat("{value|1}" + m_volUnit); } else { a.setLabelFormat("{value}" + m_volUnit); } double[] closeChange = new ArrayMath(m_closeData).delta().result(); int i = lastIndex(m_volData); string label = "Vol"; if (i >= 0) { label = label + ": " + formatValue(m_volData[i], m_generalFormat) + m_volUnit; closeChange[0] = 0; } DataSet upDS = barLayer.addDataSet(new ArrayMath(m_volData).selectGTZ(closeChange ).result(), upColor); DataSet dnDS = barLayer.addDataSet(new ArrayMath(m_volData).selectLTZ(closeChange ).result(), downColor); DataSet flatDS = barLayer.addDataSet(new ArrayMath(m_volData).selectEQZ(closeChange ).result(), flatColor); if ((i < 0) || (closeChange[i] == 0) || (closeChange[i] == Chart.NoValue)) { flatDS.setDataName(label); } else if (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. public XYChart addIndicator(int height) { //create a new chart object XYChart ret = new XYChart(m_totalWidth, height + m_topMargin + m_bottomMargin, Chart.Transparent); ret.setTrimData(m_extraPoints); if (m_currentChart != null) { //if there is a chart before the newly created chart, disable its x-axis, and copy //its x-axis labels to the new chart m_currentChart.xAxis().setColors(Chart.Transparent, Chart.Transparent); ret.xAxis().copyAxis(m_currentChart.xAxis()); //add chart to MultiChart and update the total height addChart(0, m_totalHeight + m_plotAreaGap, ret); m_totalHeight = m_totalHeight + height + 1 + m_plotAreaGap; } else { //no existing chart - create the x-axis labels from scratch setXLabels(ret.xAxis()); //add chart to MultiChart and update the total height addChart(0, m_totalHeight, ret); m_totalHeight = m_totalHeight + height + 1; } //the newly created chart becomes the current chart m_currentChart = ret; //update the size setSize(m_totalWidth, m_totalHeight + m_topMargin + m_bottomMargin); //configure the plot area ret.setPlotArea(m_leftMargin, m_topMargin, m_totalWidth - m_leftMargin - m_rightMargin, height, m_plotAreaBgColor, -1, m_plotAreaBorder).setGridColor(m_majorHGridColor, m_majorVGridColor, m_minorHGridColor, m_minorVGridColor); ret.setAntiAlias(m_antiAlias); //configure legend box LegendBox box = ret.addLegend(m_leftMargin, m_topMargin, false, m_legendFont, m_legendFontSize); box.setFontColor(m_legendFontColor); box.setBackground(m_legendBgColor); box.setMargin2(5, 0, 2, 1); box.setSize(m_totalWidth - m_leftMargin - m_rightMargin + 1, 0); //configure x-axis Axis a = ret.xAxis(); a.setIndent(true); a.setTickLength(2, 0); a.setColors(Chart.Transparent, m_xAxisFontColor, m_xAxisFontColor, m_xAxisFontColor); a.setLabelStyle(m_xAxisFont, m_xAxisFontSize, m_xAxisFontColor, m_xAxisFontAngle); //configure y-axis ret.setYAxisOnRight(m_axisOnRight); configureYAxis(ret.yAxis(), height); return ret; } private void configureYAxis(Axis a, int height) { a.setAutoScale(0, 0.05, 0); if (height < 100) { a.setTickDensity(15); } a.setMargin(m_yAxisMargin); a.setLabelStyle(m_yAxisFont, m_yAxisFontSize, m_yAxisFontColor, 0); a.setTickLength(-4, -2); a.setColors(Chart.Transparent, m_yAxisFontColor, m_yAxisFontColor, 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. public XYChart addLineIndicator(int height, double[] data, int color, string name) { XYChart c = addIndicator(height); addLineIndicator2(c, data, color, name); return c; } /// /// This method is for backward compatibility. // - use addLineIndicator(int, double[], int, string) instead. /// public XYChart addLineIndicator(int height, ArrayMath data, int color, string name) { return addLineIndicator(height, data.result(), color, name); } /// /// 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. public LineLayer addLineIndicator2(XYChart c, double[] data, int color, string name) { return c.addLineLayer(data, color, formatIndicatorLabel(name, data)); } /// /// This method is for backward compatibility. // - use addLineIndicator2(XYChart c, double[], int, string) instead. /// public LineLayer addLineIndicator2(XYChart c, ArrayMath data, int color, string name) { return addLineIndicator2(c, data.result(), color, name); } /// /// 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. public XYChart addBarIndicator(int height, double[] data, int color, string name) { XYChart c = addIndicator(height); addBarIndicator2(c, data, color, name); return c; } /// /// This method is for backward compatibility. // - use addBarIndicator(int, double[], int, string) instead. /// public XYChart addBarIndicator(int height, ArrayMath data, int color, string name) { return addBarIndicator(height, data.result(), color, name); } /// /// 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. public BarLayer addBarIndicator2(XYChart c, double[] data, int color, string name) { BarLayer layer = c.addBarLayer(data, color, formatIndicatorLabel(name, data)); layer.setBorderColor(Chart.Transparent); return layer; } /// /// This method is for backward compatibility. // - use addBarIndicator2(XYChart c, double[], int, string) instead. /// public BarLayer addBarIndicator2(XYChart c, ArrayMath data, int color, string name) { return addBarIndicator2(c, data.result(), color, name); } /// /// 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. public void addThreshold(XYChart c, LineLayer layer, double topRange, int topColor, double bottomRange, int bottomColor) { Mark topMark = c.yAxis().addMark(topRange, topColor, formatValue(topRange, m_generalFormat)); Mark bottomMark = c.yAxis().addMark(bottomRange, bottomColor, formatValue(bottomRange, m_generalFormat)); c.addInterLineLayer(layer.getLine(), topMark.getLine(), topColor, Chart.Transparent); c.addInterLineLayer(layer.getLine(), bottomMark.getLine(), Chart.Transparent, bottomColor); } private string formatIndicatorLabel(string name, double[] data) { int i = lastIndex(data); if (name == null) { return name; } if ((name == "") || (i < 0)) { return name; } string ret = name + ": " + formatValue(data[i], 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. public XYChart addAccDist(int height, int color) { //Close Location Value = ((C - L) - (H - C)) / (H - L) //Accumulation Distribution Line = Accumulation of CLV * volume double[] range = new ArrayMath(m_highData).sub(m_lowData).result(); return addLineIndicator(height, new ArrayMath(m_closeData).mul(2).sub(m_lowData).sub( m_highData).mul(m_volData).financeDiv(range, 0).acc().result(), color, "Accumulation/Distribution"); } private double[] computeAroonUp(int period) { double[] aroonUp = new double[m_highData.Length]; for(int i = 0; i < m_highData.Length; ++i) { double highValue = m_highData[i]; if (highValue == Chart.NoValue) { aroonUp[i] = Chart.NoValue; } else { int currentIndex = i; int highCount = period; int count = period; while ((count > 0) && (currentIndex >= count)) { currentIndex = currentIndex - 1; double currentValue = m_highData[currentIndex]; if (currentValue != Chart.NoValue) { count = count - 1; if (currentValue > highValue) { highValue = currentValue; highCount = count; } } } if (count > 0) { aroonUp[i] = Chart.NoValue; } else { aroonUp[i] = highCount * 100.0 / period; } } } return aroonUp; } private double[] computeAroonDn(int period) { double[] aroonDn = new double[m_lowData.Length]; for(int i = 0; i < m_lowData.Length; ++i) { double lowValue = m_lowData[i]; if (lowValue == Chart.NoValue) { aroonDn[i] = Chart.NoValue; } else { int currentIndex = i; int lowCount = period; int count = period; while ((count > 0) && (currentIndex >= count)) { currentIndex = currentIndex - 1; double currentValue = m_lowData[currentIndex]; if (currentValue != Chart.NoValue) { count = count - 1; if (currentValue < lowValue) { lowValue = currentValue; lowCount = count; } } } if (count > 0) { aroonDn[i] = Chart.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. public XYChart addAroon(int height, int period, int upColor, int downColor) { XYChart c = addIndicator(height); addLineIndicator2(c, computeAroonUp(period), upColor, "Aroon Up"); addLineIndicator2(c, 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. public XYChart addAroonOsc(int height, int period, int color) { string label = "Aroon Oscillator (" + period + ")"; XYChart c = addLineIndicator(height, new ArrayMath(computeAroonUp(period)).sub( computeAroonDn(period)).result(), color, label); c.yAxis().setLinearScale(-100, 100); return c; } private double[] computeTrueRange() { double[] previousClose = new ArrayMath(m_closeData).shift().result(); double[] ret = new ArrayMath(m_highData).sub(m_lowData).result(); double temp = 0; for(int i = 0; i < m_highData.Length; ++i) { if ((ret[i] != Chart.NoValue) && (previousClose[i] != Chart.NoValue)) { temp = Math.Abs(m_highData[i] - previousClose[i]); if (temp > ret[i]) { ret[i] = temp; } temp = Math.Abs(previousClose[i] - 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. public XYChart addADX(int height, int period, int posColor, int negColor, int color) { //pos/neg directional movement ArrayMath pos = new ArrayMath(m_highData).delta().selectGTZ(); ArrayMath neg = new ArrayMath(m_lowData).delta().mul(-1).selectGTZ(); double[] delta = new ArrayMath(pos.result()).sub(neg.result()).result(); pos.selectGTZ(delta); neg.selectLTZ(delta); //pos/neg directional index double[] tr = 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??? double[] totalDM = new ArrayMath(pos.result()).add(neg.result()).result(); ArrayMath dx = new ArrayMath(pos.result()).sub(neg.result()).abs().financeDiv(totalDM, 0 ).mul(100).expAvg(2.0 / (period + 1)); XYChart c = addIndicator(height); string label1 = "+DI (" + period + ")"; string label2 = "-DI (" + period + ")"; string label3 = "ADX (" + period + ")"; addLineIndicator2(c, pos.result(), posColor, label1); addLineIndicator2(c, neg.result(), negColor, label2); 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. public XYChart addATR(int height, int period, int color1, int color2) { double[] trueRange = computeTrueRange(); XYChart c = addLineIndicator(height, trueRange, color1, "True Range"); string label = "Average True Range (" + period + ")"; 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. public XYChart addBollingerWidth(int height, int period, double width, int color) { string label = "Bollinger Width (" + period + ", " + width + ")"; return addLineIndicator(height, new ArrayMath(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. public XYChart addCCI(int height, int period, int color, double range, int upColor, int downColor) { //typical price double[] tp = new ArrayMath(m_highData).add(m_lowData).add(m_closeData).div(3).result(); //simple moving average of typical price double[] smvtp = new ArrayMath(tp).movAvg(period).result(); //compute mean deviation double[] movMeanDev = new double[smvtp.Length]; for(int i = 0; i < smvtp.Length; ++i) { double avg = smvtp[i]; if (avg == Chart.NoValue) { movMeanDev[i] = Chart.NoValue; } else { int currentIndex = i; int count = period - 1; double acc = 0; while ((count > 0) && (currentIndex >= count)) { currentIndex = currentIndex - 1; double currentValue = tp[currentIndex]; if (currentValue != Chart.NoValue) { count = count - 1; acc = acc + Math.Abs(avg - currentValue); } } if (count > 0) { movMeanDev[i] = Chart.NoValue; } else { movMeanDev[i] = acc / period; } } } XYChart c = addIndicator(height); string label = "CCI (" + period + ")"; LineLayer layer = addLineIndicator2(c, new ArrayMath(tp).sub(smvtp).financeDiv( movMeanDev, 0).div(0.015).result(), color, label); 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. public XYChart addChaikinMoneyFlow(int height, int period, int color) { double[] range = new ArrayMath(m_highData).sub(m_lowData).result(); double[] volAvg = new ArrayMath(m_volData).movAvg(period).result(); string label = "Chaikin Money Flow (" + period + ")"; return addBarIndicator(height, new ArrayMath(m_closeData).mul(2).sub(m_lowData).sub( m_highData).mul(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. public XYChart addChaikinOscillator(int height, int color) { //first compute acc/dist line double[] range = new ArrayMath(m_highData).sub(m_lowData).result(); double[] accdist = new ArrayMath(m_closeData).mul(2).sub(m_lowData).sub(m_highData).mul( m_volData).financeDiv(range, 0).acc().result(); //chaikin osc = exp3(accdist) - exp10(accdist) double[] expAvg10 = new ArrayMath(accdist).expAvg(2.0 / (10 + 1)).result(); return 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. public XYChart addChaikinVolatility(int height, int period1, int period2, int color) { string label = "Chaikin Volatility (" + period1 + ", " + period2 + ")"; return addLineIndicator(height, new ArrayMath(m_highData).sub(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. public XYChart addCLV(int height, int color) { //Close Location Value = ((C - L) - (H - C)) / (H - L) double[] range = new ArrayMath(m_highData).sub(m_lowData).result(); return addLineIndicator(height, new ArrayMath(m_closeData).mul(2).sub(m_lowData).sub( 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. public XYChart addDPO(int height, int period, int color) { string label = "DPO (" + period + ")"; return addLineIndicator(height, new ArrayMath(m_closeData).movAvg(period).shift(period / 2 + 1).sub(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. public XYChart addDonchianWidth(int height, int period, int color) { string label = "Donchian Width (" + period + ")"; return addLineIndicator(height, new ArrayMath(m_highData).movMax(period).sub( new ArrayMath(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. public XYChart addEaseOfMovement(int height, int period, int color1, int color2) { double[] boxRatioInverted = new ArrayMath(m_highData).sub(m_lowData).financeDiv( m_volData, 0).result(); double[] result = new ArrayMath(m_highData).add(m_lowData).div(2).delta().mul( boxRatioInverted).result(); XYChart c = addLineIndicator(height, result, color1, "EMV"); string label = "EMV EMA (" + period + ")"; 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. public XYChart addFastStochastic(int height, int period1, int period2, int color1, int color2) { double[] movLow = new ArrayMath(m_lowData).movMin(period1).result(); double[] movRange = new ArrayMath(m_highData).movMax(period1).sub(movLow).result(); double[] stochastic = new ArrayMath(m_closeData).sub(movLow).financeDiv(movRange, 0.5 ).mul(100).result(); string label1 = "Fast Stochastic %K (" + period1 + ")"; XYChart c = addLineIndicator(height, stochastic, color1, label1); string label2 = "%D (" + period2 + ")"; 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. public XYChart addMACD(int height, int period1, int period2, int period3, int color, int signalColor, int divColor) { XYChart c = addIndicator(height); //MACD is defined as the difference between two exponential averages (typically 12/26 days) double[] expAvg1 = new ArrayMath(m_closeData).expAvg(2.0 / (period1 + 1)).result(); double[] macd = new ArrayMath(m_closeData).expAvg(2.0 / (period2 + 1)).sub(expAvg1 ).result(); //Add the MACD line string label1 = "MACD (" + period1 + ", " + period2 + ")"; addLineIndicator2(c, macd, color, label1); //MACD signal line double[] macdSignal = new ArrayMath(macd).expAvg(2.0 / (period3 + 1)).result(); string label2 = "EXP (" + period3 + ")"; addLineIndicator2(c, macdSignal, signalColor, label2); //Divergence 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. public XYChart addMassIndex(int height, int color, int upColor, int downColor) { //Mass Index double f = 2.0 / (10); double[] exp9 = new ArrayMath(m_highData).sub(m_lowData).expAvg(f).result(); double[] exp99 = new ArrayMath(exp9).expAvg(f).result(); XYChart c = 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. public XYChart addMFI(int height, int period, int color, double range, int upColor, int downColor) { //Money Flow Index double[] typicalPrice = new ArrayMath(m_highData).add(m_lowData).add(m_closeData).div(3 ).result(); double[] moneyFlow = new ArrayMath(typicalPrice).mul(m_volData).result(); double[] selector = new ArrayMath(typicalPrice).delta().result(); double[] posMoneyFlow = new ArrayMath(moneyFlow).selectGTZ(selector).movAvg(period ).result(); double[] posNegMoneyFlow = new ArrayMath(moneyFlow).selectLTZ(selector).movAvg(period ).add(posMoneyFlow).result(); XYChart c = addIndicator(height); string label = "Money Flow Index (" + period + ")"; LineLayer layer = addLineIndicator2(c, new ArrayMath(posMoneyFlow).financeDiv( posNegMoneyFlow, 0.5).mul(100).result(), color, label); 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. public XYChart addMomentum(int height, int period, int color) { string label = "Momentum (" + period + ")"; return addLineIndicator(height, new ArrayMath(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. public XYChart addNVI(int height, int period, int color, int signalColor) { double[] nvi = new double[m_volData.Length]; double previousNVI = 100; double previousVol = Chart.NoValue; double previousClose = Chart.NoValue; for(int i = 0; i < m_volData.Length; ++i) { if (m_volData[i] == Chart.NoValue) { nvi[i] = Chart.NoValue; } else { if ((previousVol != Chart.NoValue) && (m_volData[i] < previousVol) && ( previousClose != Chart.NoValue) && (m_closeData[i] != Chart.NoValue)) { nvi[i] = previousNVI + previousNVI * (m_closeData[i] - previousClose) / previousClose; } else { nvi[i] = previousNVI; } previousNVI = nvi[i]; previousVol = m_volData[i]; previousClose = m_closeData[i]; } } XYChart c = addLineIndicator(height, nvi, color, "NVI"); if (nvi.Length > period) { string label = "NVI SMA (" + period + ")"; 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. public XYChart addOBV(int height, int color) { double[] closeChange = new ArrayMath(m_closeData).delta().result(); double[] upVolume = new ArrayMath(m_volData).selectGTZ(closeChange).result(); double[] downVolume = new ArrayMath(m_volData).selectLTZ(closeChange).result(); return 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. public XYChart addPerformance(int height, int color) { double closeValue = firstCloseValue(); if (closeValue != Chart.NoValue) { return addLineIndicator(height, new ArrayMath(m_closeData).mul(100 / closeValue ).sub(100).result(), color, "Performance"); } else { //chart is empty !!! return 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. public XYChart addPPO(int height, int period1, int period2, int period3, int color, int signalColor, int divColor) { double[] expAvg1 = new ArrayMath(m_closeData).expAvg(2.0 / (period1 + 1)).result(); double[] expAvg2 = new ArrayMath(m_closeData).expAvg(2.0 / (period2 + 1)).result(); ArrayMath ppo = new ArrayMath(expAvg2).sub(expAvg1).financeDiv(expAvg2, 0).mul(100); double[] ppoSignal = new ArrayMath(ppo.result()).expAvg(2.0 / (period3 + 1)).result(); string label1 = "PPO (" + period1 + ", " + period2 + ")"; string label2 = "EMA (" + period3 + ")"; XYChart c = addLineIndicator(height, ppo.result(), color, label1); addLineIndicator2(c, ppoSignal, signalColor, label2); 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. public XYChart addPVI(int height, int period, int color, int signalColor) { //Positive Volume Index double[] pvi = new double[m_volData.Length]; double previousPVI = 100; double previousVol = Chart.NoValue; double previousClose = Chart.NoValue; for(int i = 0; i < m_volData.Length; ++i) { if (m_volData[i] == Chart.NoValue) { pvi[i] = Chart.NoValue; } else { if ((previousVol != Chart.NoValue) && (m_volData[i] > previousVol) && ( previousClose != Chart.NoValue) && (m_closeData[i] != Chart.NoValue)) { pvi[i] = previousPVI + previousPVI * (m_closeData[i] - previousClose) / previousClose; } else { pvi[i] = previousPVI; } previousPVI = pvi[i]; previousVol = m_volData[i]; previousClose = m_closeData[i]; } } XYChart c = addLineIndicator(height, pvi, color, "PVI"); if (pvi.Length > period) { string label = "PVI SMA (" + period + ")"; 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. public XYChart addPVO(int height, int period1, int period2, int period3, int color, int signalColor, int divColor) { double[] expAvg1 = new ArrayMath(m_volData).expAvg(2.0 / (period1 + 1)).result(); double[] expAvg2 = new ArrayMath(m_volData).expAvg(2.0 / (period2 + 1)).result(); ArrayMath pvo = new ArrayMath(expAvg2).sub(expAvg1).financeDiv(expAvg2, 0).mul(100); double[] pvoSignal = new ArrayMath(pvo.result()).expAvg(2.0 / (period3 + 1)).result(); string label1 = "PVO (" + period1 + ", " + period2 + ")"; string label2 = "EMA (" + period3 + ")"; XYChart c = addLineIndicator(height, pvo.result(), color, label1); addLineIndicator2(c, pvoSignal, signalColor, label2); 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. public XYChart addPVT(int height, int color) { return addLineIndicator(height, new ArrayMath(m_closeData).rate().sub(1).mul(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. public XYChart addROC(int height, int period, int color) { string label = "ROC (" + period + ")"; return addLineIndicator(height, new ArrayMath(m_closeData).rate(period).sub(1).mul(100 ).result(), color, label); } private double[] RSIMovAvg(double[] data, int period) { //The "moving average" in classical RSI is based on a formula that mixes simple //and exponential moving averages. if (period <= 0) { period = 1; } int count = 0; double acc = 0; for(int i = 0; i < data.Length; ++i) { if (Math.Abs(data[i] / Chart.NoValue - 1) > 1e-005) { count = count + 1; acc = acc + data[i]; if (count < period) { data[i] = Chart.NoValue; } else { data[i] = acc / period; acc = data[i] * (period - 1); } } } return data; } private double[] computeRSI(int 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. double[] absChange = RSIMovAvg(new ArrayMath(m_closeData).delta().abs().result(), period ); double[] absUpChange = RSIMovAvg(new ArrayMath(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. public XYChart addRSI(int height, int period, int color, double range, int upColor, int downColor) { XYChart c = addIndicator(height); string label = "RSI (" + period + ")"; LineLayer layer = addLineIndicator2(c, computeRSI(period), color, label); //Add range if given if ((range > 0) && (range < 50)) { 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. public XYChart addSlowStochastic(int height, int period1, int period2, int color1, int color2) { double[] movLow = new ArrayMath(m_lowData).movMin(period1).result(); double[] movRange = new ArrayMath(m_highData).movMax(period1).sub(movLow).result(); ArrayMath stochastic = new ArrayMath(m_closeData).sub(movLow).financeDiv(movRange, 0.5 ).mul(100).movAvg(3); string label1 = "Slow Stochastic %K (" + period1 + ")"; string label2 = "%D (" + period2 + ")"; XYChart c = addLineIndicator(height, stochastic.result(), color1, label1); 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. public XYChart addStdDev(int height, int period, int color) { string label = "Moving StdDev (" + period + ")"; return addLineIndicator(height, new ArrayMath(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. public XYChart addStochRSI(int height, int period, int color, double range, int upColor, int downColor) { double[] rsi = computeRSI(period); double[] movLow = new ArrayMath(rsi).movMin(period).result(); double[] movRange = new ArrayMath(rsi).movMax(period).sub(movLow).result(); XYChart c = addIndicator(height); string label = "StochRSI (" + period + ")"; LineLayer layer = 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)) { 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. public XYChart addTRIX(int height, int period, int color) { double f = 2.0 / (period + 1); string label = "TRIX (" + period + ")"; return addLineIndicator(height, new ArrayMath(m_closeData).expAvg(f).expAvg(f).expAvg(f ).rate().sub(1).mul(100).result(), color, label); } private double[] computeTrueLow() { //the lower of today's low or yesterday's close. double[] previousClose = new ArrayMath(m_closeData).shift().result(); double[] ret = new double[m_lowData.Length]; for(int i = 0; i < m_lowData.Length; ++i) { if ((m_lowData[i] != Chart.NoValue) && (previousClose[i] != Chart.NoValue)) { if (m_lowData[i] < previousClose[i]) { ret[i] = m_lowData[i]; } else { ret[i] = previousClose[i]; } } else { ret[i] = Chart.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. public XYChart addUltimateOscillator(int height, int period1, int period2, int period3, int color, double range, int upColor, int downColor) { double[] trueLow = computeTrueLow(); double[] buyingPressure = new ArrayMath(m_closeData).sub(trueLow).result(); double[] trueRange = computeTrueRange(); double[] rawUO1 = new ArrayMath(buyingPressure).movAvg(period1).financeDiv( new ArrayMath(trueRange).movAvg(period1).result(), 0.5).mul(4).result(); double[] rawUO2 = new ArrayMath(buyingPressure).movAvg(period2).financeDiv( new ArrayMath(trueRange).movAvg(period2).result(), 0.5).mul(2).result(); double[] rawUO3 = new ArrayMath(buyingPressure).movAvg(period3).financeDiv( new ArrayMath(trueRange).movAvg(period3).result(), 0.5).mul(1).result(); XYChart c = addIndicator(height); string label = "Ultimate Oscillator (" + period1 + ", " + period2 + ", " + period3 + ")" ; LineLayer layer = addLineIndicator2(c, new ArrayMath(rawUO1).add(rawUO2).add(rawUO3 ).mul(100.0 / 7).result(), color, label); 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. public XYChart addVolIndicator(int height, int upColor, int downColor, int flatColor) { XYChart c = addIndicator(height); 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. public XYChart addWilliamR(int height, int period, int color, double range, int upColor, int downColor) { double[] movLow = new ArrayMath(m_lowData).movMin(period).result(); double[] movHigh = new ArrayMath(m_highData).movMax(period).result(); double[] movRange = new ArrayMath(movHigh).sub(movLow).result(); XYChart c = addIndicator(height); LineLayer layer = addLineIndicator2(c, new ArrayMath(movHigh).sub(m_closeData ).financeDiv(movRange, 0.5).mul(-100).result(), color, "William %R"); addThreshold(c, layer, -50 + range, upColor, -50 - range, downColor); c.yAxis().setLinearScale(-100, 0); return c; } /// /// This method is for backward compatibility. It has no purpose. /// public void layoutChart() { //do nothing } } }