Home > Technology > Strategy Example D

EXAMPLE D : Creating Custom Components

Although implementing functions using formulas code is a straight forward process, it sometimes makes sense to implement new functionality in the form of a custom component. Reasons for doing so may be:

A component is an instance of a class that inherits from class Component. Once implemented, components can be used from within formula code via instances of class Handle.

Components can have 'inputs' as well as 'outputs' which correspond to the arguments and return values of formula functions. Although a component can be used directly from within formula code, it is customary to also implement a creator function, also refered to as a 'wrapper function', which allows the component's functionality to be used via more easily used formula syntax!

At the simplest level, a new component needs a constructor which defines its inputs and outputs, and it needs to override the virtual Evaluate() member. As an example, a basic 'moving average' component implementation would look as follows: 

class DemoAverageComp : public Component

{

      size_t m_period;

public:

      DemoAverageComp(size_t _period) : m_period(_period){

            AddInput("data", T_DOUBLE, _period);

            AddOutput("average", T_DOUBLE);

      }

      virtual void Evaluate(void){

            double dSum = 0.0;

            for(size_t c=0; c< m_period; c++){ 

                  dSum += (double)GetAt(0,c);

            }

            SetAt(0, dSum / (double)m_period);

      }

};

In practice however, most components use optimized algorithms where functionality is split between initialization code that resides in the override of a virtual InitialEvaluate() member and the code that is evaluated on subsequent intervals via the virtual Evaluate() member.

Once the initial value for a moving average is known, the next value can be calculated merely by knowing the most recent and oldest data values. Such 'fast' algorithms have substantially less overhead but must be reinitialized periodically to avoid accumulating floating point errors.

A more complete implementation of a moving average component thus looks as follows:

class DemoAverageComp : public TSA::Component //1

{

      size_t m_period;  //2

      double m_dSum;    //3 variable for sum

public:

      DemoAverageComp(size_t _period) //4

      {

            AddInput("data", T_DOUBLE, _period + 1); //5 defining the single input

            AddOutput("average", T_DOUBLE);          //6 defining the single output

            m_period = _period;                      //7 remembering the period

      }

      virtual void InitialEvaluate(void)          //8  Initial evaluation

      {

            m_dSum = 0.0;                         //9

            for(size_t c=0; c< m_period; c++){    //10   

                  m_dSum += (double)GetAt(0,c);   //11 calculating the sum

            }

            SetAt(0, m_dSum / (double)m_period);  //12 setting the average as output

      }

      virtual void Evaluate(void)                 //13  for all subsequent evaluations

      {

            m_dSum += (double)GetAt(0,0);         //14  updating the sum

            m_dSum -= (double)GetAt(0, m_period); //15  ibid

            SetAt(0, m_dSum / (double)m_period);  //16  setting the average as output

      }

};

The class constructor declares the component to have a single input in //5 and a single output in //6, both of internal type T_DOUBLE (corresponding to interface class Double)

This implementation features both the virtual InitialEvaluate() as well as the virtual Evaluate() members in //8 and //13 respectively. The virtual InitialEvaluate() member calculates the moving average using the iterative method which relies on the for-loop in //10 to calculating the sum which is then divided by the period in //12. The output (or 'return' value) is then set at index 0 via the SetAt() member.

The virtual Evaluate() member now uses a shortcut to update the sum, in //14 and //15, before again dividing by the period in //16. In order for this shortcut method to work, the sum must be remembered between calls. This is the purpose behind the member variable 'm_dSum', declared in //3.

Note how the current and past input values are accessed via the GetAt() member. When algorithms, such as  those used for pattern recognition, need to access large data arrays, it is also possible to access an entire data array via a single pointer to eliminate the overhead caused by repeated calls to GetAt().

The Lookback value

Users of formula functions do not need to worry about allocating vector memory. Such convenience is only possible because the required vector length must be defined when component inputs are created, in this case in line //5

      AddInput("data", T_DOUBLE, _period + 1); //5  last argument is 'lookback' value

The calculation of a normal moving average of period 20 normally requires 20 data items (same as the period). In this particular case, because the shortcut method is used, the algorithm requires one more data item in line //15.

Defining the 'Wrapper' Function

Now that the component has been implemented, it is time to implement the corresponding formula function. In this particular case the corresponding 'wrapper' function is quite simple:

Double Demo_Average(const DoubleVector& data, size_t _period) //1

{

      Handle c = new DemoAverageComp(_period); //2 period passed as constructor argument

      c.GetSocket("data") << data;      //3 binding data vector to component input

      c.SetCreatorName("DemoAverage");  //4

      return c("average"); //5 returning the 'average' output

}

In //2 an instance of class DemoAverageComp is allocated and immediately assigned to an instance of class Handle, which provides a uniform interface to all type of components for binding operations, such as in //3 and //4. The Component's base class constructor immediately registers itself with the strategy object, and although the Handle instance goes out of scope at the end of the Demo_Average() function, the component itself is not deleted and its lifetime is managed by the strategy as well as by any interface type object that references it, such as the Double object returned by the function in line //5 (actually its a Variant implicitly cast to Double). The DemoAverageComp instance allocated in line //2 will only be deleted once the last object referencing it, which is likely to be the strategy object, goes out of scope.

Usage

The new formula function, 'wrapping' class DemoAverageComp's functionality is now ready for use in formula code as in the following code snippet:

            Strategy t;

            (. . .)

            DoubleVector randDbl = RandDouble();

            Double ma10  = Demo_Average(randDbl, 10); //calculating moving average

            (. . .) 

 

            t.Evaluate( D(2004, MAR, 1), D(2004, APR, 1));

 

 


     Copyright (c) 2008 PERITECH. All Rights Reserved.