// regression-channel.afl
// Part 5 - Trendlines and Channels
//
// A trend channel with no anchor points to choose. The centre line is the
// end-point of a least-squares fit over a fixed window; the band is a multiple
// of the standard deviation of price around its own mean over that window.
//
// Two people running this on the same symbol, the same interval and the same
// window get the same channel. That is the entire argument for it: it moves the
// free parameters out of your hands (which pivots to anchor to, wick or body)
// and into two numbers you have to write down (window length, band width).
//
// This is not a claim that a regression channel describes the market better
// than a hand-drawn trendline. It is a claim that it is reproducible, and that
// a hand-drawn line is not.
//
// Assumptions:
//   - daily bars, at least Window + 1 bars of history;
//   - prices greater than zero when fitting in log space;
//   - the first Window - 1 bars are warm-up and carry no channel.

_SECTION_BEGIN("Regression channel");

Window   = Param( "Regression window (bars)", 60, 10, 400, 5 );
BandMult = Param( "Band width (x StDev)", 2, 0.5, 4, 0.25 );
UseLog   = ParamToggle( "Fit in", "Price|Log price", 0 );

// Fitting the logarithm makes the channel a constant-percentage path instead of
// a constant-currency one - the same choice as linear versus semi-log scaling
// on the chart itself, and it produces a visibly different channel on any long
// history.
if ( UseLog )
{
    Source = log( Max( Close, 0.01 ) );
}
else
{
    Source = Close;
}

Centre = LinearReg( Source, Window );
Spread = StDev( Source, Window );

Upper = Centre + BandMult * Spread;
Lower = Centre - BandMult * Spread;

if ( UseLog )
{
    Centre = exp( Centre );
    Upper  = exp( Upper );
    Lower  = exp( Lower );
}

Plot( Close, "Close", colorDefault, styleCandle );
Plot( Centre, "Regression centre", colorOrange, styleLine | styleThick );
Plot( Upper, "Upper band", colorBlueGrey, styleLine );
Plot( Lower, "Lower band", colorBlueGrey, styleLine );

// Where is price inside the channel? Zero at the centre, +1 at the upper band,
// -1 at the lower. SafeDivide keeps a zero-width band from producing Null.
Position = SafeDivide( Close - Centre, Upper - Centre, 0 );

// The slope of the fit, expressed as a percentage of price per bar, so that the
// number means the same thing on any instrument.
SlopePct = 100 * SafeDivide( LinRegSlope( Source, Window ),
                             IIf( UseLog, 1, Close ), 0 );

Title = Name() + "  regression channel, " + Window + " bars, +/-" +
        WriteVal( BandMult, 1.2 ) + " StDev" +
        WriteIf( UseLog, ", fitted in log price", ", fitted in price" ) +
        "  |  position in channel " + WriteVal( Position, 1.2 ) +
        "  |  slope " + WriteVal( SlopePct, 1.3 ) + "% per bar";

_SECTION_END();
