Advanced - State Keeping

Paintbar as a State Machine

 

The paintbars (and indicators in general) are, internally, state machines. What that means in abstract is that there is a saved "state", the paintbar or indicator calculation is performed based on that state and the candle that is passed to the calculation, then a new state is generated (that includes the output of the paintbar or indicator).

 

For simple paintbars, that state machinery is hidden, because it is implicit. The SetColor etc. functions set the current state, and the Open, Close, etc. indexed variables as well as Indicator Variables are passed in as part of the current state.

 

PB - advanced - state - ribbon

If you want to do something more complex, the state machinery needs to be explicit. The Advanced Paintbar Editor will add the state-keeping functions in for you - that functionality is in the ribbon menu's State tab.

 

State-keeping Internals

 

There are several parts to the state-keeping in the paintbars:

 

1. The State - it consists of

 


PaintbarState struct that can contain any number of value types. It is very important that only value types are included in that struct, not reference types. There are two instances of the struct - CurrentState and SavedState. All the calculation should be performed using the CurrentState, the SavedState is only there for state-keeping.

 

For any objects/classes that you need for the state (that is, reference types), you have to do the state-keeping yourself. That would include defining the current/saved copy of the object and the deep-copying between them in the state-saving and state-restoring functions. We provide a very useful class FIFOQueue that would probably be sufficient for most of your needs.

 

2. The initialization functions PaintbarInitialize and PaintbarClearState.

 


PaintbarClearState is only executed at the start of the calculation (that is, before the first candle of the chart is passed to the MainCalculation function), and should be used to initialize the state variables.

 

PaintbarInitialize is also executed at the start of the calculation and should include any other initializations required.

 

3. The state-keeping functions PaintbarSaveState and PaintbarRestoreState.

 


By default, when Advanced Paintbar Editor creates these functions in your code, they just copy CurrentState to SavedState and vice versa. If you have any other state-keeping variables, their copying should be inside these two functions as well.

 

 

Example with Standard State-Keeping

 

Let's say you want to create a paintbar that takes a 10-period EMA of the EMA that's on the chart, and shows positive color if close is above that line and negative if it is below. Here is the code that would do it (assume that the variable EMA_Line is the Pre-Defined Indicator Variable for the chart's EMA indicator.

 

double fEMAMult; // EMA Multiplier

 

/// <summary>
/// Is called at start of paintbar calculation, should be used to initialize anything needed for the paintbar
/// </summary>
private void PaintbarInitialize()
{
   fEMAMult = 2.0D/(10+1.0D);
}
 
/// <summary>
/// Holds paintbar state - fill with variables needed to be preserved for next paintbar calc
/// </summary>
private struct PaintbarState
{
  public double NowEMA;
}
 
/// <summary>
/// Holds current PB state - use to calc PB, changes to it carry over to next PB calc
/// </summary>
private PaintbarState CurrentState;
 
/// <summary>
/// Holds saved PB state - internal
/// </summary>
private PaintbarState SavedState;
 
/// <summary>
/// Is called at start of paintbar calculation, should be used to clear the paintbar state
/// </summary>
private void PaintbarClearState()
{
   CurrentState = new PaintbarState();
   CurrentState.NowEMA = double.MinValue;
}
 
/// <summary>
/// Saves paintbar state (called internally).
/// </summary>
private void PaintbarSaveState()
{
   SavedState = CurrentState;
}
 
/// <summary>
/// Restores paintbar state (called internally).
/// </summary>
private void PaintbarRestoreState()
{
   CurrentState = SavedState;
}
 
public void MainCalculation()
{
  // calculate the EMA of the EMA
  if (CurrentState.NowEMA == double.MinValue)
   CurrentState.NowEMA = EMA_Line;
  else
   CurrentState.NowEMA += fEMAMult * (EMA_Line - CurrentState.NowEMA);
 
  if (Close > CurrentState.NowEMA)
      SetColor("Above", SysColor.Positive);
  else
      SetColor("Below", SysColor.Negative);
}

 

Example with FIFOQueue State-Keeping

 

Let's re-do the above example, with the only difference is that instead of 10-period EMA, we will do a 10-period SMA. We will use the FIFOQueue class to keep and calculate the SMA.

 

FIFOQueue SavedSMA;
FIFOQueue NowSMA;
 
/// <summary>
/// Is called at start of paintbar calculation, should be used to initialize anything needed for the paintbar
/// </summary>
private void PaintbarInitialize()
{
 
}
 
/// <summary>
/// Holds paintbar state - fill with variables needed to be preserved for next paintbar calc
/// </summary>
private struct PaintbarState
{
}
 
/// <summary>
/// Holds current PB state - use to calc PB, changes to it carry over to next PB calc
/// </summary>
private PaintbarState CurrentState;
 
/// <summary>
/// Holds saved PB state - internal
/// </summary>
private PaintbarState SavedState;
 
/// <summary>
/// Is called at start of paintbar calculation, should be used to clear the paintbar state
/// </summary>
private void PaintbarClearState()
{
   CurrentState = new PaintbarState();
   SavedSMA     = new FIFOQueue(10);
   NowSMA       = new FIFOQueue(10);
}
 
/// <summary>
/// Saves paintbar state (called internally).
/// </summary>
private void PaintbarSaveState()
{
   SavedState = CurrentState;
   NowSMA.CopyTo(SavedSMA);
}
 
/// <summary>
/// Restores paintbar state (called internally).
/// </summary>
private void PaintbarRestoreState()
{
   CurrentState = SavedState;
   SavedSMA.CopyTo(NowSMA);
}
 
public void MainCalculation()
{
  // calculate the SMA of the EMA
   NowSMA.Add(EMA_Line);
 
  if (Close > NowSMA.Average)
      SetColor("Above", SysColor.Positive);
  else
      SetColor("Below", SysColor.Negative);
}