Posted 12/8/2006 06:35:31
|
|
|
|
At the request of another member, I've decided to post the source code to another indicator to serve as an example. This is our On Balance Volume code, slightly modified for clarity.using System; using System.Collections.Generic; using System.Text; using RightEdge.Common;namespace MyIndicators { [YYEIndicatorAttribute(System.Drawing.KnownColor.Crimson, YYEIndicatorAttribute.EIndicatorGroup.Volume, Name = "OBV", Description = "My On Balance Volume implementation", Id = "Generate a unique ID here", DefaultDrawingPane = "OBVPane", HelpText = "Put some detailed text here if desired.")] [Serializable] public class MyOBV : IndicatorBase { private double previousValue = 0.0; private List<BarData> pBars = null; /// Constructs an On Balance Volume indicator instance. public MyOBV() { pBars = new List<BarData>(); } /// Calculates the values for an entire series. public override List<double> CalcSeriesValues(IList<BarData> bars) { pBars = new List<BarData>(); return base.CalcSeriesValues(bars); } /// Calculates the next value in the indicator series. public override double CalcNextValue(BarData bar) { pBars.Add(bar); int pBarIndex = pBars.Count - 1;
if (pBarIndex == 0) { // This is our first non-empty bar previousValue = double.NaN; return double.NaN; } else { double todaysClose = bar.Close; double yesterdaysClose = pBars[pBarIndex - 1].Close; double todaysVolume = bar.Volume; if (double.IsNaN(previousValue)) { previousValue = todaysVolume; return todaysVolume; } else { double yesterdaysOBV = previousValue; if (todaysClose > yesterdaysClose) { previousValue = yesterdaysOBV + todaysVolume; return yesterdaysOBV + todaysVolume; } else { previousValue = yesterdaysOBV - todaysVolume; return yesterdaysOBV - todaysVolume; } } } } } }
The meat is really in CalcNextValue(). This is called each time a new bar comes in, so we perform the calculation and return the value. CalcSeriesValues() is used for optimizing an entire series of calculations. In other words, many indicators can be calculated independently, while some rely on previously calculated values. If you can store those previously calc'ed values instead of having to recalculate them everytime, you save a lot of cycles. That's really what this function is for. We don't need it in this case. The attributes at the top of the class are used for the user interface and persistence. We're specifying a default color of Crimson, we're asking RightEdge to place this in the "Volume" tree node in the indicator pane, it will show up as "OBV" in the indicator list. Now the ID is important because this is what RightEdge uses to identify this indicator so that it can save and load it from a saved chart for example. We typically generate a GUID as the ID. You can generate this ID in Visual Studio if you're familiar with that tool, or use the one at http://www.guidgen.com. DefaultDrawingPane proposes a default pane name to the user when they drag and drop this indicator. HelpText is the text that is displayed when a user selects Indicator Information from the indicator pane.
|
|
Posted 12/10/2006 21:30:55
|
|
|
|
| Would it be possible to get an example in VB?
|
|
Posted 12/11/2006 08:34:13
|
|
|
|
Sure. Here's the code (I think my VB conversion is pretty close). I've also attached the project file for your convenience.Imports System Imports System.Collections.Generic Imports System.Text Imports RightEdge.Common Imports RightEdge.IndicatorsNamespace MyIndicators<YYEIndicatorAttribute(System.Drawing.KnownColor.Crimson, YYEIndicatorAttribute.EIndicatorGroup.Volatility, Author:= "You", CompanyName:="", DefaultDrawingPane:="OBVPane", Description:="Your Indicator Description here", GroupName:="", HelpText:="Add help text that will be displayed in the RightEdge user interface here.", Id:="<Generate a unique ID here>", Name:="Your Indicator Name Here", Version:="0.1")> _<Serializable()> _ <SeriesInputAttribute(Name:= "Input", Order:=1, Value:=BarElement.Close)> _Public Class MyRightEdgeIndicator Inherits SeriesCalculatorBaseWithValues
Private periods As Integer = 20 Private shift As Double = 5 Private sma As SMA = Nothing Private validCount As Integer = 0
Public Sub New(ByVal periods As Integer, ByVal shift As Double) MyBase.New(1) Me.periods = periods Me.shift = shift sma = New SMA(periods) End Sub Protected Overloads Overrides Function CalcNewValue(ByVal index As Integer) As Double System.Math.Min(System.Threading.Interlocked.Increment(validCount), validCount - 1) If validCount < periods Then Return Double.NaN End If Dim price As Double = inputs(0)(index) Return price - (sma(index) * (shift / 100)) End Function
Protected Overloads Overrides Sub Reset() validCount = 0 End Sub
Public Overloads Overrides Sub NewBar() sma.NewBar() MyBase.NewBar() End Sub
Public Overloads Overrides Sub NewSeries(ByVal count As Integer) sma.NewSeries(count) MyBase.NewSeries(count) End Sub
Public Overloads Overrides Sub SetInputs(ByVal ParamArray newInputs As ISeries()) sma.SetInputs(newInputs) MyBase.SetInputs(newInputs) End Sub End Class End Namespace
|
|
Posted 12/11/2006 18:19:04
|
|
|
|
| Thanks for that. Just one question for now. How would I add a signal line to that moving average? Can I do it through code, or do I have to manually add two SMA's to the chart?
|
|
Posted 12/11/2006 18:25:03
|
|
|
|
| There's only one series per indicator. So you'd have to build another indicator that calculates the signal line, or you can do it through the user interface. In other words, you would be your indicator and for the series line, "chain" this indicator to an SMA. There's a good example of this using the MACD and a MACD signal line here: http://www.rightedgesystems.com/forums/FindPost575.aspx BigBerner (12/11/2006)
Thanks for that. Just one question for now. How would I add a signal line to that moving average? Can I do it through code, or do I have to manually add two SMA's to the chart?
|
|
Posted 12/11/2006 19:10:10
|
|
|
|
| OK thanks. One more question. I don't really understand the line of code Dim price as Double = inputs(0)(index). Can you explain the inputs(0)(index) part - I can't see where they are coming from.
|
|
Posted 12/11/2006 19:38:16
|
|
|
|
| Sure, the inputs come from the SeriesCalculatorBase class. This is the core of what allows indicators to be chained. Since this indicator is derived from SeriesCalculatorBaseWithValues, which is derived from SeriesCalculatorBase, the inputs variable is populated for you. This gives you the input parameter. In cases where you can have multiple inputs, this array would hold those values. Here's some of the relevant documentation. http://www.rightedgesystems.com/documentation/developerguide/RightEdge.Common.SeriesCalculatorBaseMembers.html Note, if you want a really simple indicator that takes one input and doesn't need to support chaining with another indicator (in other words, will always take a piece of bar data), derive from IndicatorBase. Keep firing off the questions as they come up. I know getting your head around the various classes is a bit of a task at first. Hopefully you'll find that they make life easy.
|
|
|
|