/*
 *  Configurable moving average - Part 10 project.
 *
 *  What it does
 *    Plots one, and optionally two, moving averages of a price field you pick,
 *    using an averaging method you pick, with an optional slope colouring and
 *    optional markers on the bars where the two averages cross.
 *
 *  Assumptions and limits
 *    - Chart pane formula. It produces no Buy/Sell signals and does no trading.
 *    - Warm-up bars are blanked on purpose. MA returns Null for its first
 *      periods-1 bars, but DEMA is seeded from the first data point and so
 *      returns numbers immediately; without blanking, the early values of the
 *      lag-reduced averages look valid when they are not.
 *    - A moving average is an average of past prices. Its level and its
 *      direction describe where price has been. They are not a forecast.
 *    - Averaging methods differ in how they are seeded, so two methods can
 *      disagree for hundreds of bars at the start of a symbol's history.
 */

_SECTION_BEGIN( "Configurable MA" );

MaTypeName = ParamList( "Average type",
                        "Simple|Exponential|Weighted|Double exponential|Triple exponential|Wilders",
                        0 );
Source      = ParamField( "Price field", 3 );
MaPeriod    = Param( "Periods", 50, 2, 400, 1, 10 );
MaTint      = ParamColor( "Colour", colorCycle );
MaStyleBits = ParamStyle( "Style", styleLine | styleThick, maskDefault );

ColourBySlope = ParamToggle( "Colour by slope", "No|Yes", 0 );
RisingTint    = ParamColor( "Rising colour", colorBrightGreen );
FallingTint   = ParamColor( "Falling colour", colorRed );
SlopeBars     = Param( "Slope measured over (bars)", 3, 1, 50, 1 );

ShowSecond   = ParamToggle( "Second average", "No|Yes", 1 );
SecondPeriod = Param( "Second periods", 200, 2, 600, 1 );
SecondTint   = ParamColor( "Second colour", colorBlueGrey );
MarkCrosses  = ParamToggle( "Mark crosses", "No|Yes", 1 );

/*
 *  Returns the chosen average. ParamList hands back the label as text, so the
 *  choice is made by string comparison. Anything unrecognised falls back to the
 *  simple average rather than leaving the result Null and quietly emptying the
 *  chart. AFL requires the single return to be the last statement in the body.
 */
function AverageOf( DataSeries, Periods, TypeName )
{
    local Result;

    if( TypeName == "Exponential" )             Result = EMA( DataSeries, Periods );
    else if( TypeName == "Weighted" )           Result = WMA( DataSeries, Periods );
    else if( TypeName == "Double exponential" ) Result = DEMA( DataSeries, Periods );
    else if( TypeName == "Triple exponential" ) Result = TEMA( DataSeries, Periods );
    else if( TypeName == "Wilders" )            Result = Wilders( DataSeries, Periods );
    else                                        Result = MA( DataSeries, Periods );

    return Result;
}

/*
 *  Hides the bars before the average has seen a full window of data, so that
 *  every method starts drawing at the same bar and none of them shows a value
 *  that is really an artefact of its own seeding.
 */
function BlankWarmUp( DataSeries, Periods )
{
    local Result;

    Result = IIf( BarIndex() >= Periods, DataSeries, Null );

    return Result;
}

Primary = BlankWarmUp( AverageOf( Source, MaPeriod, MaTypeName ), MaPeriod );

Plot( Close, "Price", colorDefault,
      styleCandle | styleNoTitle | GetPriceStyle() );

// A per-bar colour array turns one plot into a two-colour plot. The comparison
// is against the average's own value SlopeBars ago, not against price, so the
// colour reports the average's direction rather than the market's.
Slope = Primary - Ref( Primary, -SlopeBars );

if( ColourBySlope ) LineTint = IIf( Slope > 0, RisingTint, FallingTint );
else                LineTint = MaTint;

Plot( Primary, MaTypeName + " " + NumToStr( MaPeriod, 1.0 ),
      LineTint, MaStyleBits );

if( ShowSecond )
{
    GraphXSpace = 8;

    Secondary = BlankWarmUp( AverageOf( Source, SecondPeriod, MaTypeName ), SecondPeriod );

    Plot( Secondary, MaTypeName + " " + NumToStr( SecondPeriod, 1.0 ),
          SecondTint, styleLine );

    if( MarkCrosses )
    {
        // ExRem keeps one marker per change of side when the two averages sit
        // on top of each other and cross repeatedly.
        CrossUp   = ExRem( Cross( Primary, Secondary ), Cross( Secondary, Primary ) );
        CrossDown = ExRem( Cross( Secondary, Primary ), Cross( Primary, Secondary ) );

        // Anchored to the average itself, with a zero pixel offset, so the
        // marker sits exactly on the crossing point.
        PlotShapes( IIf( CrossUp, shapeSmallCircle, shapeNone ),
                    RisingTint, 0, Primary, 0 );
        PlotShapes( IIf( CrossDown, shapeSmallCircle, shapeNone ),
                    FallingTint, 0, Primary, 0 );
    }
}

_N( Title = StrFormat(
        "%s   %s(%g) = %g   change over %g bars = %g   (an average of past prices, not a forecast)",
        Name(), MaTypeName, MaPeriod,
        SelectedValue( Primary ), SlopeBars, SelectedValue( Slope ) ) );

_SECTION_END();
