Spectrum on Number of Stocks

Dear Team,

Refering an earlier topic Exact Number of Stocks above and Below MA

I wish to add the spectrum analysis of stocks in a given watchlist that segregates them into number & percentage of stocks above and below a MA on following lines. Suppose the watchlist has 500 stocks, and let us suppose we are considering a MA (200 EMA for understanding purposes for a 'param') then number of stocks above their respective 200 EMAs by more than 15%, between +10 to +5%, between +5 to +3% , between +3 to 0% and similarly stocks below their respective 200 EMAs by more than 15%, between -10 to -5%, between -5 to -3% , between -3 to 0%.

Another variation would be on individual stock charts that shows the bell curve of the variation from the 200 DMA and what is the current position. e.g. if currently the chart is below the 200 DMA by say 7% and the bell curve indicates that the 2 sigma range is +6% to -6%

I did something similar but purely on price variation. I have Amibroker exploration dump and perl script to do a tally of the spread and dump into excel. The charts will look bell shape with mean around zero i.e. mean reversion.
Nov 15th, 2024 (As you can see, the Healthcare-Biotech crashed.)

Nov 4th, 2024

Dear Peter,

Thanks for your reply. While the excel output looks quiet interesting, for many price analysts (Those who look only at price & the patterns) the statistics might be difficult to fathom &/or reconstruct due to limited programming skills.

The idea is to construct an indicator, more or less on lines below (Am attaching the image as well as the code)

_SECTION_BEGIN("MA Tags");
P = ParamField("Price field",-1);
Periods = Param("Periods", 15, 2, 300, 1, 10 );
EMADiff = C-EMA(P,Periods);
EMADiffPer = EMADiff / C * 100;
Plot(EMADiffPer,_DEFAULT_NAME(),ParamColor( "Color", colorCycle ),styleLine);

Plot(10,"10 L",colorRed,styleLine | styleNoTitle);
Plot(5,"5 L",colorBlack,styleLine | styleNoTitle);
Plot(3,"3 L",colorBlue,styleLine | styleNoTitle | styleDashed);
Plot(0,"0 L",colorBlack,styleLine | styleNoTitle);
Plot(-3,"-3 L",colorBlue,styleLine | styleNoTitle | styleDashed);
Plot(-5,"-5 L",colorBlack,styleLine | styleNoTitle);
Plot(-10,"-10 L",colorRed,styleLine | styleNoTitle);

_SECTION_END();

What we want to now superimpose is the bell curve (Horizontally in this case) over the period visble in the chart.

Part 2 is constructing an exploration that gives the spread of number of stocks in a watchlist that are within a range of percentages from the 200 EMA.
For this I am reproducing the code that plots the number of stocks above the MAs 20-50-200

_SECTION_BEGIN("Moving Avergae 20-50-200");
/// Set symbol list in Analysis Filter - Include
/// And enable pad&align
/// Set Periodicity in analysis General settings
/// Create composite ARRAY via SCAN 
/// @link https://forum.amibroker.com/t/percentage-stock-price-nyse-above-the-200-day-moving-average/19377/8
/// by AmiBroker.com and fxshrat@gmail.com
list_name = CategoryGetName(categoryGroup, 5);
symbol_name = "~AboveMA_"+list_name;
cnt = "~cnt_"+list_name;
periods = MxFromString("[20;50;200]");// MA periods
rows = MxGetSize(periods, 0);
persist = False;
if ( Status( "action" ) == actionScan ) 
{
    /// derived from AB manual
    /// @link https://www.amibroker.com/guide/afl/staticvaradd.html
    if ( Status( "stocknum" ) == 0 ) 
    {
        // remove any earier composite values
        StaticVarRemove(symbol_name+"*");
        StaticVarRemove(cnt);
    }   
    // Iterating periods
    for ( i = 0; i < rows; i++ ) 
		StaticVarAdd(symbol_name+"_"+ i, C >= MA(C, periods[i][0]), True, persist);
		StaticVarAdd(cnt, 1, True, persist);
    Buy = 0;
}

AddColumn(C,"Close",1.2);
colors = "colorGreen,colorBlue,colorRed";

// New code for display_name
currentWatchlistIndex = GetOption("FilterIncludeWatchlist");
if (currentWatchlistIndex >= 0) {
    display_name = CategoryGetName(categoryWatchlist, currentWatchlistIndex);
} else {
    display_name = "No Watchlist Selected"; // Default text when no watchlist is selected
}

// Iterating periods
for ( i = 0; i < rows; i++ ) 
{
	Stocks_aboveMA = Nz(StaticVarGet(symbol_name+"_"+i));
	WeekStocks=ValueWhen(DayOfWeek() > Ref( DayOfWeek(),1),Stocks_aboveMA);
	pcnt_aboveMA = Nz(StaticVarGet(symbol_name+"_"+i) / StaticVarGet(cnt))*100;
	titles = StrFormat("Percentage > %g-day MA", periods[i][0]);
	Plot(pcnt_aboveMA, "Percentage > " + periods[i][0] + "-day MA (" + display_name + ")", VarGet(StrExtract(colors,i)), styleThick);
	Filter = 1;
	AddColumn(Stocks_aboveMA,"Count+"+periods[i][0],1.0);
	AddColumn(pcnt_aboveMA,"Percent"+periods[i][0],1.2);
}
Plot(10,"10 L",colorBlack,styleLine | styleNoTitle);
Plot(30,"30 L",colorBlue,styleLine | styleNoTitle | styleDashed);
Plot(50,"50 L",colorBlack,styleLine | styleNoTitle);
Plot(70,"70 L",colorBlue,styleLine | styleNoTitle | styleDashed);
Plot(90,"90 L",colorBlack,styleLine | styleNoTitle);


SetSortColumns(-2);
_SECTION_END();

Courtesy @fxshrat

The plot of 2nd code in above post looks something like below

Similarly we can have 8 line charts representing the number of stocks with EMADiffPer between 0 to 3, 3 to 5, 5 to 10, above 10 and similarly 0 to -3, -3 to -5, -5 to -10, below -10

This IMHO would be a good guage of the strength of the markets

@pushkan, here's my attempt to codify your idea (I'll leave it to other users to evaluate its usefulness).

Suggestions or reports of errors that need to be corrected are always welcome.

The formula must be applied to the analysis with a single "watchlist" in the "Filter" with a bar range greater than 225 must be chosen.
Then, select the "Sequence" option.
Once this is done, you can view the result in a new chart pane.
By opening the parameters window, you can select different lengths to view the results based on the selected moving average (multiple MAs are calculated in the scan) and a couple of alternative visualization modes.

There is probably room for improvement; in any case, the sections of code marked with
// CUSTOMIZE THESE LINES AS NEEDED
indicates where the user must make changes in the code if he prefers to display other reference values.

#pragma sequence(scan,explore)
/// https://forum.amibroker.com/t/spectrum-on-number-of-stocks/39473
///
/// Using dynamic variables to avoid code duplication
/// This sample code is free for any use



/// Apply to "Filter" - Select a Watchlist - Execute a Sequence
lastUsedMAPeriods = NZ( StaticVarGet( "~DynRangesMA_Len" ), 50 );

// CUSTOMIZE THESE LINES AS NEEDED
MAPeriodsStart = 25;
MAPeriodsEnd = 250;
MAPeriodsStep = 25;

MAPeriods = Param( "MA Periods", lastUsedMAPeriods, MAPeriodsStart, MAPeriodsEnd, MAPeriodsStep );
ReverseUnderMA = ParamToggle( "Reverse % values under MA", "No|Yes", 1 );
PlotValuesOrPercent = ParamToggle( "Plot values or percent of them", "Values|Percent", 1 );
symbol_name = "~DynRangesMA_";
cnt = "~DynRangesCnt_";
MALenUsed = "~DynRangesMA_Len";
MAWLUsed = "~DynRangesMA_WL";

// CUSTOMIZE THESE LINES AS NEEDED
ranges = MxFromString( "[-1000;-20;-15;-10;-5;0;5;10;15;20;1000]" );
rangesLabels = "-20)|[-20..-15)|[-15..-10)|[-10..-5)|[-5..0)|[0..+5)|[5..+10)|[10..+15)|[15..+20)|[20";
// user def colors
colorPaleOrange = ColorRGB( 255, 205, 187 );
colorPalePink = colorRGB( 255, 214, 222 );
rangeColors = "DarkRed|Red|Orange|PaleOrange|PalePink|PaleGreen|Lime|Green|DarkGreen";

rows = MxGetSize( ranges, 0 );
bc = BarCount;

if( Status( "action" ) == actionScan )
{
    if( Status( "stocknum" ) == 0 )
    {
        // remove any earier composite values
        StaticVarRemove( symbol_name + "*" );
        StaticVarRemove( cnt + "*" );
        StaticVarSet( "~DynRangesMA_Len", MAPeriods );
        currentWLIndex = GetOption( "FilterIncludeWatchlist" );

        if( currentWLIndex >= 0 )
        {
            WLName = CategoryGetName( categoryWatchlist, currentWLIndex );
        }
        else
        {
            WLName = "";
        }

        StaticVarSetText( "~DynRangesMA_WL", WLName );

    }

    // Iterating MA Periods
    for( j = MAPeriodsStart; j <= MAPeriodsEnd; j += MAPeriodsStep )
    {
        MAArray = MA( C, j );
        MADiff = C - MAArray;
        MAValue = ( MADiff / C ) * 100;

        // Iterating ranges
        for( i = 0; i < rows - 1; i++ )
        {
            StaticVarAdd( symbol_name + "_" + i + "_" + j, iif( MAValue >= ranges[i][0] AND MaValue < ranges[i + 1][0], 1, 0 ) );
        }

        StaticVarAdd( cnt + "_" + j, iif( MAValue >= ranges[0][0] AND MAValue <= ranges[rows - 1][0], 1, 0 ) );

    }

    Buy = 0;
}

style = styleThick;
DT = DateTime();

// EXPLORATION/PLOT
// You can repeat/change the exploration - without doing a new scan - changing the MAPeriod in the "Analysis Paramaters" dialog
// You can change the chart disaplyed values setting the MAPeriod in the "Chart Paramaters" dialog
Filter = 1; // Status( "lastbarinrange" );
tot = 0;  // used to verify the # of valid (not empty) MA arrays for each date
totPcnt = 0; // should always add to 100%
count = StaticVarGet( cnt + "_" + MAPeriods );
SetOption( "NoDefaultColumns", True );
AddColumn( MAPeriods, "MA Periods", 1 );
AddColumn( Dt, "Date", formatDateTimeISO );
AddTextColumn( Name(), "Ticker" );

for( i = 0; i < rows - 1; i++ )
{
    from  = ranges[i][0];
    to    = ranges[i + 1][0];
    sign_ = iif( from < 0, -1, 1 );
    // _TRACEF( "Index: %g - From: %g - to: %g", i, ranges[i][0], ranges[i + 1][0] );
    value = NZ( StaticVarGet( symbol_name + "_" + i + "_" + MAPeriods ) );

    pcnt  = iif( ReverseUnderMA, sign_, 1 ) * ( value / count ) * 100;
    tot += value;
    totPcnt += abs( pcnt );
    label = StrExtract( rangesLabels, i, '|' );
    color = VarGet( "color" + StrExtract( rangeColors, i, '|' ) );
    AddColumn( value, label );
    AddColumn( pcnt, "%" + label );

    if( PlotValuesOrPercent )
        Plot( pcnt, label, color, style );
    else
        Plot( iif( ReverseUnderMA, sign_, 1 ) * value, label, color, style );
}

if (ReverseUnderMA)
	PlotGrid( 0, colorGrey50 );

// Values to verify correctness
AddColumn( totPcnt, "Tot %" );
AddColumn( tot, "Tot", 1 );
AddColumn( count, "Count", 1 );

// single instrument values
MAArray = MA( C, MAPeriods );
MADiff = C - MAArray;
MAValue = ( MADiff / C ) * 100;
AddColumn( C, "Close" );
AddColumn( MAArray, "MA" );
AddColumn( MADiff, "MA diff" );
AddColumn( MAValue, "% diff" );

SetSortColumns( -2, 3 );


WLName = StaticVarGetText( "~DynRangesMA_WL" );
_N( Title = WriteIf( WLName != "", WLName + " - ", "" ) + "% Relative to MA(" + MAPeriods + ") - Stock Count: " + SelectedValue( Count ) +
            " - {{INTERVAL}} {{DATE}} - {{VALUES}}" );

The code logic was inspired by the second example provided in your last post.

1 Like

Given you are conducting market breath and indicator status/metrics for the overall market and or market segments, you might find it useful to know Norgate / PremiumData includes some 380 aggregated breath and indicator datasets as symbols. It make it very easy, to not only reference them via foreign, but also to measure them or apply indicators to those data series as well, as a trading/action filter etc...

Sorry I included a list of 380 of them, but it was too invasive and long for this thread, so I deleted it.

@Sean, I agree, but many users here don't have a service similar to Norgate for their local markets (Europe, UK, India, Hong Kong, etc.).

Nothing prevents users from modifying the script, avoiding static variables, creating dedicated tickers, as was widely done in the past, using the function AddToComposite() - In such a case this document will still provide a good overview of its functionality.