// screener-stage-audit.afl
// Part 12 - Project: Build a Daily Stock Screener (validation formula)
//
// Answers one question the screener itself cannot: where does the universe
// go? Each column is the CUMULATIVE conjunction of the stages up to that
// point, so the TOTAL summary row reads as a funnel - how many symbols were
// examined, how many survived stage 1, how many survived stages 1 and 2, and
// so on. Run it before you trust a candidate list, and again whenever you
// change a threshold.
//
// Read the funnel like this:
//   - a stage that removes almost nothing is not doing any work; either the
//     threshold is too loose or the stage restates an earlier one
//   - a stage that removes almost everything is where an empty candidate list
//     comes from, and is the only stage worth arguing about
//   - "Examined" far below your universe size means the run never saw the
//     symbols you thought it did: check Apply to, and check "Not enough data"
//
// Assumptions and thresholds are copied deliberately from daily-screener.afl.
// If you change one there, change it here; a stage audit that measures a
// different pipeline from the one you run is worse than no audit.

Exclude = StrLeft( Name(), 1 ) == "~";

MinPrice        = 5;
LiquidityPeriod = 50;
MinTurnover     = 1000000;
TrendPeriod     = 200;
TrendSlopeBars  = 20;
MomentumPeriod  = 60;
MinMomentum     = 0;
AtrPeriod       = 20;
MinAtrPct       = 1;
MaxAtrPct       = 8;
SetupPeriod     = 10;

AvgTurnover = MA( Close * Volume, LiquidityPeriod );
Trend       = MA( Close, TrendPeriod );
Momentum    = ROC( Close, MomentumPeriod );
AtrValue    = ATR( AtrPeriod );
AtrPct      = 100 * AtrValue / Close;

HasHistory = NOT IsNull( Trend ) AND NOT IsNull( Momentum ) AND
             NOT IsNull( AvgTurnover ) AND NOT IsNull( AtrValue );

// IsTrue() converts "unknown" to 0 rather than letting Null spread through the
// conjunction. Without it, a single unwarmed indicator turns every downstream
// column into Null and the funnel reads as though the stage rejected the
// symbol, which is a different finding entirely.
Pass0 = IsTrue( HasHistory );
Pass1 = IsTrue( Pass0 AND Close > MinPrice );
Pass2 = IsTrue( Pass1 AND AvgTurnover > MinTurnover );
Pass3 = IsTrue( Pass2 AND Close > Trend AND Trend > Ref( Trend, -TrendSlopeBars ) );
Pass4 = IsTrue( Pass3 AND Momentum > MinMomentum );
Pass5 = IsTrue( Pass4 AND AtrPct > MinAtrPct AND AtrPct < MaxAtrPct );
Pass6 = IsTrue( Pass5 AND Close >= HHV( Close, SetupPeriod ) AND
                Close > Ref( Close, -1 ) );

Filter = Status( "lastbarinrange" );

AddColumn( Pass0, "0 has history", 1.0 );   // column 3
AddColumn( Pass1, "1 price",       1.0 );   // column 4
AddColumn( Pass2, "2 liquidity",   1.0 );   // column 5
AddColumn( Pass3, "3 trend",       1.0 );   // column 6
AddColumn( Pass4, "4 momentum",    1.0 );   // column 7
AddColumn( Pass5, "5 volatility",  1.0 );   // column 8
AddColumn( Pass6, "6 setup",       1.0 );   // column 9

// TOTAL gives the survivor count for each stage; COUNT gives the number of
// symbols the run actually examined. Both rows appear at the TOP of the list.
AddSummaryRows( 1 + 16, 1.0, 3, 4, 5, 6, 7, 8, 9 );
