/*
 *  Trend regime classifier - Part 10 project.
 *
 *  What it does
 *    Labels every bar as UP, DOWN or RANGE from two objective measurements:
 *    the slope of a long average, expressed in ATR units per bar so that it is
 *    comparable across instruments, and the side of that average price is on.
 *    A label is only issued once the condition has held for a set number of
 *    consecutive bars.
 *
 *  What it is not
 *    - It is not a forecast. Every input is a function of bars that have
 *      already closed, so the label describes the recent past.
 *    - It has irreducible lag. The average needs TrendPeriod bars, the slope is
 *      measured over SlopeBars bars, and the confirmation needs ConfirmBars
 *      bars, so a change of regime can only be announced well after the market
 *      began to change. The title reports that lag on every bar.
 *    - The label on the current, unfinished bar can change before the bar
 *      closes. Only completed bars carry a settled label.
 *
 *  Assumptions
 *    - Chart pane formula, applied to the price pane.
 *    - Any interval. On very short intervals the ATR normalisation still works,
 *      but the confirmation window should be widened.
 */

_SECTION_BEGIN( "Trend regime" );

TrendPeriod    = Param( "Trend average periods", 100, 20, 400, 5 );
SlopeBars      = Param( "Slope measured over (bars)", 20, 2, 100, 1 );
SlopeThreshold = Param( "Slope threshold (ATR per bar x 100)", 5, 0, 50, 1 ) / 100;
AtrPeriod      = Param( "ATR periods", 20, 2, 100, 1 );
ConfirmBars    = Param( "Confirmation bars", 5, 1, 30, 1 );
Sticky         = ParamToggle( "Carry last direction through ranges", "No|Yes", 0 );
ShowBackground = ParamToggle( "Regime background", "No|Yes", 1 );
ShowChanges    = ParamToggle( "Mark regime changes", "No|Yes", 1 );

UpTint    = ParamColor( "Up colour", colorPaleGreen );
DownTint  = ParamColor( "Down colour", colorRose );
RangeTint = ParamColor( "Range colour", colorLightGrey );

TrendLine  = MA( Close, TrendPeriod );
Volatility = ATR( AtrPeriod );

// Slope in ATR units per bar. Dividing by ATR is what makes the same threshold
// mean the same thing on a 12-dollar share and a 4,000-point index.
Slope = ( TrendLine - Ref( TrendLine, -SlopeBars ) ) / ( SlopeBars * Volatility );

RawUp   = Slope >  SlopeThreshold AND Close > TrendLine;
RawDown = Slope < -SlopeThreshold AND Close < TrendLine;

// Confirmation: the raw condition must have been true on every one of the last
// ConfirmBars bars. Sum() of a Boolean array counts the true bars in the window.
ConfirmedUp   = Sum( RawUp,   ConfirmBars ) == ConfirmBars;
ConfirmedDown = Sum( RawDown, ConfirmBars ) == ConfirmBars;

// One array holds the whole classification: 1 up, -1 down, 0 range.
Direction = IIf( ConfirmedUp, 1, IIf( ConfirmedDown, -1, 0 ) );

// Optional stickiness: carry the last confirmed direction forward instead of
// falling back to RANGE. This makes the chart calmer and the classifier less
// honest, because a stale label is still displayed as a current one.
if( Sticky ) Regime = Nz( ValueWhen( Direction != 0, Direction ), 0 );
else         Regime = Direction;

RegimeTint = IIf( Regime == 1, UpTint, IIf( Regime == -1, DownTint, RangeTint ) );

// AmiBroker has no per-bar pane background. The documented way to colour bars
// behind everything else is a full-height own-scale area plot: the constant 1
// against a 0..1 scale fills the pane, and ZOrder -2 puts it behind the grid,
// which sits at ZOrder 0.
if( ShowBackground )
    Plot( 1, "Regime background", ColorBlend( RegimeTint, GetChartBkColor(), 0.75 ),
          styleArea | styleOwnScale | styleNoLabel | styleNoTitle,
          0, 1, 0, -2 );

Plot( Close, "Price", colorDefault, styleCandle | styleNoTitle | GetPriceStyle() );
Plot( TrendLine, "Trend " + NumToStr( TrendPeriod, 1.0 ), colorBlueGrey, styleLine | styleThick );

// A ribbon repeats the label at the foot of the pane, so the regime is still
// readable when the background is switched off or the theme is dark.
Plot( 2, "Regime", RegimeTint,
      styleArea | styleOwnScale | styleNoLabel | styleNoTitle, -0.5, 100 );

Changed = Regime != Ref( Regime, -1 );

if( ShowChanges )
{
    GraphXSpace = 8;
    PlotShapes( IIf( Changed, shapeSmallSquare, shapeNone ),
                colorDarkGrey, 0, High, 14 );
}

// The structural lag, stated in the tool itself rather than in a footnote.
// The confirmation window and the slope window are both explicit; the averaging
// window adds more lag on top of these two, which is why this is a minimum.
MinimumLag = ConfirmBars + SlopeBars;

RegimeWord = WriteIf( Regime == 1, "UP",
                      WriteIf( Regime == -1, "DOWN", "RANGE" ) );

_N( Title = StrFormat(
        "%s   regime: %s for %g bars   slope %g ATR/bar   minimum structural lag %g bars\n"
        + "This is a description of bars that have already closed, not a forecast.",
        Name(), RegimeWord,
        SelectedValue( BarsSince( Changed ) ),
        SelectedValue( Slope ), MinimumLag ) );

_SECTION_END();
