Log Scaling Based Point & Figure AFL Charting

Hi All -

The greatest version of P&F implementation in Amibroker I could find is by Mr. Graham Kavanagh in 2004. The design of this AFL is based on ATR which will cause the Chart to Repaint/ Change with every new Bar. This will make entire system Instable, nither will the Signals be Consistent.

https://www.amibroker.com/members/library/detail.php?id=348

On another hand the Challenge with Fixed Fixed/ Absolute Scaling is that it's suitable for only one Asset class, and such scaling can not be used for asset classes nither for diffrent categories of Large cap/ mid cap/ small / Micro cap. Hence, its not a cover it all Approach.

The Best Practiced Approach is "Log Based Scaling" Which allows user to set a certain Percentage as Scaling e.g. 1%, now irrespective the the Closing Price All Asset classes and Market cap based categorized stocks will have 1% Box Size. This is the Best Practiced Cover it all Approach.

I tried to edit Graham's AFL but this will eventually give Log Scaling based on Lastvalue(C) which will again has all repainting issues like his ATR() based appraoch has.

Has anyone been able to figure out the Log Scaling Based Approach?

Sincerely Appreciate Any response to this Please. :pray:

@fxshrat @Milosz @empottasch @beppe @nsm51

Or anyone else...

only thing I did with PnF is the Python code I posted. I looked it up if they offer this but it seems not. About a "dynamic ATR" the author writes the following, see second post:

dynamic ATR second post

There is apparently someone who solved your request:, see:

he says: ", I even have the percentage (Log/Ln) box version done". Maybe ask him

1 Like

Hi there, there is no difference between the common log base 10 method and the natural log base e method. Here is a Royal Bank of Canada natural log base e P&F chart, with e = 2.718281828459 plotted using the Richard D. Wyckoff 1 box reversal box count: same column if a retracement is only 1 box (in this case, you will see both the x and the o plotted in the same column).

1 Like

Here it is plotted using log base 10, as you can see, they have the same result.

1 Like

Thanks @empottasch
Thanks @Clipper

Dear Clipper - What I am specifically looking for is AFL implementation (AFL Code) of PnF Charting based on Log Percentage scaling Using Closing. Such Approach has dynamic Boz sizing and hence the Chart doesn't get repainted or change when new bars show up. I did not Mean log scaling as natural log as such. Even I am not sure why its still called log approach, all i know is its dynamic approach where box is computed based on every bar closing Price and not remain static (based on percentage or ATR of last bar Closing price) like our Veteran Mr. Graham Once codes in 2004. Appreciate if I could get the AFL code.

Hi there, sorry I don’t know what you mean by dynamic Boz. Is the word Boz means some kind of buzz word for a fancy trading methodology you try to implement? Can you show a picture of what do you mean by Boz as in dynamic Boz? Because I don’t know what Boz stands for. Point is, after you implemented the P&F chart, you can create some options like below. Here is a P&F charting based on Log Percentage scaling using closing like you said. The chart doesn’t get repainted or change when new boxes are added. So as you can see in the scaling, every increment is 1% and is based on Log Percentage, but the scale is showing closing prices. If you look down at the Y axis, the first price is 78.12, then every increment after that is exactly 1%, so, 78.12 x 1.01=78.90, 78.90 x 1.01=79.69, then 80.49, 81.29, 82.10, 82.93, and so on. Hope that help.

Dear @Clipper - Boz was a typo for BOX.

What I am looking for is a P&F AFL that considers the Closing price of bar (not just lastvalue(C) but each closing price) and calculates x% (user definable param) as the Size of the box. So instead of making an absolute size of the Box (say $1 or $20) we are making box size as the fixed %age of closing price. So now every box size will be x% of the Closing price of a particular bar.

The advantage of this approach is we can dynamically make P&F any asset class/ market cap based on their inherent volatility. e.g. 0.5% for Index chart, 2% for Mid Cap stock, 1% for Large Cap stock, 0.15% for Relative Strength chart etc.

Most Standard Pureplay PnF Services like Nasdaq Dorsey Wright, Bulls Eyes Broker, Definedge etc use this approach only. so if the Chart is 1% Scale with 3 Box reversal they as per standard call that chart a "1% x 3 PnF Chart".

So essentially by log percentage they rather mean fixed Percentage of closing price and not arithmetic LOG() as such.

Does your implementation have similar Approach? Is it Possible if I can have a look at your AFL please?

Hi, like I said before, in my Point and Figure chart, 1 box = 100 basis point (if you look up at the title, a little left of the text where it said Box Method: Close). This means 1 box = exactly 1%, because 100 basis = 1%. So if you see a 5 boxes move up or down, it means a 5% move up or down. You can verify this by looking at my y axis as I said before. My Y axis starts (in the picture) with 78.12, then the next box up was 78.90, the next box up 79.69, then 80.49, then 81.29.

What this means is 78.90 + 1% = 79.69; 79.69 + 1% = 80.49; 80.49 + 1% = 81.29. And so on.

To repeat, every box in my chart is 1%, so a 15 boxes means 15% move. It is exactly what you are looking for.

The solution you are looking for is very easy and simple to implement.

here is an alternative idea. Just use your regular swing type of indicator and take out the time factor using sparsecompress. Here an example using some ATR swing code. Now it will not repaint and still it takes out the time:

SetBarsRequired( sbrAll, sbrAll );

GfxSetZOrder( -5 );
GfxSetCoordsMode( 1 );

per = Param( "ATR Period", 10, 1, 500, 1 );
mult = Param( "ATR Multiple", 3, 0.1, 100, 0.1 );
priceswitch = ParamToggle( "Select", "Close|High or Low", 0 );

bi = BarIndex();
fvb = FirstVisibleValue( bi );
lvb = LastVisibleValue( bi );
pk = tr = 0;
trailarrayup = trailarraydn = Null;

if( priceswitch )
{
    prch = H;
    prcl = L;
}
else
{
    prch = C;
    prcl = C;
}

// if ticksize is not set in code or information window use 0.01
if( TickSize == 0 )
{
    TickSize = 0.01;
}

// ATR
maatrper = mult * ATR( per );
sup = round( ( prch - maatrper ) / TickSize ) * TickSize;
res = round( ( prcl + maatrper ) / TickSize ) * TickSize;

trend = 1;
topprc = 0;
topidx = 0;
botprc = 0;
botidx = 0;

for( i = per + 1; i < BarCount; i++ )
{
    if( trend > 0 )
    {
        // trend turning down, avoid same bar as the peak
        if( prcl[i] < trailarrayup[i - 1] ) //AND prch[i] < topprc )
        {
            pk[topidx] = 1;
            botprc = prcl[i];
            botidx = i;
            trend = -1;
            trailarraydn[i] = res[i];
            trailarrayup[i] = trailarrayup[i - 1];
        }
        else

            // still in uptrend but new top reached
            if( prch[i] >= topprc )
            {
                topprc = prch[i];
                topidx = i;
                trailarrayup[i] = Max( trailarrayup[i - 1], sup[i] );
                pkn[i] = 1;
            }
            // continuation inside uptrend
            else
            {
                trailarrayup[i] = Max( trailarrayup[i - 1], sup[i] );
            }
    }
    else
        if( trend < 0 )
        {
            // trend turning up, avoid same bar as the trough
            if( prch[i] > trailarraydn[i - 1] )// AND prcl[i] > botprc )
            {
                tr[botidx] = 1;
                topprc = prch[i];
                topidx = i;
                trend = 1;
                trailarrayup[i] = sup[i];
                trailarraydn[i] = trailarraydn[i - 1];
            }
            else

                // still in downtrend but new trough reached
                if( prcl[i] <= botprc )
                {
                    botprc = prcl[i];
                    botidx = i;
                    trailarraydn[i] = Min( trailarraydn[i - 1], res[i] );
                    trn[i] = 1;
                }
                // continuation inside downtrend
                else
                {
                    trailarraydn[i] = Min( trailarraydn[i - 1], res[i] );
                }
        }
}

// last pivot
lastidxpk = LastValue( ValueWhen( pk, bi ) );
lastidxtr = LastValue( ValueWhen( tr, bi ) );
lastvalpk = LastValue( ValueWhen( pk, prch ) );
lastvaltr = LastValue( ValueWhen( tr, prcl ) );
valpk = LastValue( HighestSince( Ref( tr, -1 ), prch , 1 ) );
idxpk = LastValue( ValueWhen( prch == valpk, bi ) );
valtr = LastValue( LowestSince( Ref( pk, -1 ), prcl, 1 ) );
idxtr = LastValue( ValueWhen( prcl == valtr, bi ) );
lvtrailup = LastValue( ValueWhen( !IsEmpty( trailarrayup ), trailarrayup ) );
lvtraildn = LastValue( ValueWhen( !IsEmpty( trailarraydn ), trailarraydn ) );

if( lastidxpk > lastidxtr AND LastValue( prcl ) < lvtrailup )
{
    tr[idxtr] = 1;
}

if( lastidxpk < lastidxtr AND LastValue( prch ) > lvtraildn )
{
    pk[idxpk] = 1;
}

SetChartBkColor( ColorRGB( 0, 0, 0 ) );
xx = SparseCompress( pk OR tr, IIf( pk, prch, prcl ) );

Plot( xx, "", colorwhite, styleDots, Null, Null, 0, 1, 1 );

for( i = lvb; i > fvb; i-- )
{
    if( !IsEmpty( xx[i] ) AND xx[i] > xx[i - 1] )
    {
        GfxSelectSolidBrush( colorBlue );
        GfxRectangle( i - 0.5 , xx[i], i + 0.5, xx[i - 1] );
    }

    if( !IsEmpty( xx[i] ) AND xx[i] < xx[i - 1] )
    {
        GfxSelectSolidBrush( colorRed );
        GfxRectangle( i - 0.5 , xx[i], i + 0.5, xx[i - 1] );
    }
}

Title = "ATR VALUE: " + maatrper;


1 Like

Dear @empottasch

Sincerely, Appreciate your implementation.

I have changed one thing in your AFL, which is from AFL to Pct based scaling.

// ATR
//maatrper = mult * ATR( per );

boxpct = Param("Box Pct", 1, 0.25, 30, 0.25);
maatrper = C * boxpct / 100;

So, what I am trying to do is to make every box size a certain Percentage of Closing Price.

Now if you can follow the Attached GIF, your can see as the move the bars (from bar reply) in chart there is repaint of bars. No?

PnF_Pct

Dear @Clipper -

Yes, your implementation is what I am looking for.

I have used Graham's Apr'2004 AFL as base (https://www.amibroker.com/members/library/detail.php?id=348) and replaced the ATR computation with the % of Closing Price.

boxpct = param("boxpct", 0.25, 0.25, 10, 0.25);
CX = C * boxpct /100;
CF = ceil(Cx);
CR = floor(Cx);

But having done so, the Chart repaints/changes when new Bars shows up. (validated in Bar Reply). I am not able to debut that.

Appreciate if you can share your AFL implementation.

ok yes on the edge. I took some other code and took some code out that I thought was not necessary. Maybe see if this version is better

SetBarsRequired( sbrAll, sbrAll );

GfxSetZOrder( -5 );
GfxSetCoordsMode( 1 );

per = Param( "ATR Period", 10, 1, 500, 1 );
mult = Param( "ATR Multiple", 3, 0.1, 100, 0.1 );
priceswitch = ParamToggle( "Select", "Close|High or Low", 0 );

bi = BarIndex();
fvb = FirstVisibleValue( bi );
lvb = LastVisibleValue( bi );
pk = tr = Buy = Sell = BuyPrice = SellPrice = pkn = trn = 0;
trailarrayup = trailarraydn = Null;

if( priceswitch )
{
    prch = H;
    prcl = L;
}
else
{
    prch = C;
    prcl = C;
}

// if ticksize is not set in code or information window use 0.01
if( TickSize == 0 )
{
    TickSize = 0.01;
}

// ATR
maatrper = mult * ATR( per );
//boxpct = Param("Box Pct", 7, 0.25, 30, 0.25);
//maatrper = C * boxpct / 100;
sup = round( ( prch - maatrper ) / TickSize ) * TickSize;
res = round( ( prcl + maatrper ) / TickSize ) * TickSize;

trend = 1;
topprc = 0;
topidx = 0;
botprc = 0;
botidx = 0;
slip = 0;

for( i = per + 1; i < BarCount; i++ )
{
    if( trend > 0 )
    {
        // trend turning down, avoid same bar as the peak
        if( prcl[i] < trailarrayup[i - 1] ) //AND prch[i] < topprc )
        {
            pk[topidx] = 1;
            botprc = prcl[i];
            botidx = i;
            trend = -1;
            Sell[i] = 1;
            SellPrice[i] = C[i] - slip;
            trailarraydn[i] = res[i];
            trailarrayup[i] = trailarrayup[i - 1];
        }
        else

            // still in uptrend but new top reached
            if( prch[i] >= topprc )
            {
                topprc = prch[i];
                topidx = i;
                trailarrayup[i] = Max( trailarrayup[i - 1], sup[i] );
                pkn[i] = 1;
            }
            // continuation inside uptrend
            else
            {
                trailarrayup[i] = Max( trailarrayup[i - 1], sup[i] );
            }
    }
    else
        if( trend < 0 )
        {
            // trend turning up, avoid same bar as the trough
            if( prch[i] > trailarraydn[i - 1] )// AND prcl[i] > botprc )
            {
                tr[botidx] = 1;
                topprc = prch[i];
                topidx = i;
                trend = 1;
                Buy[i] = 1;
                BuyPrice[i] = C[i] + slip;
                trailarrayup[i] = sup[i];
                trailarraydn[i] = trailarraydn[i - 1];
            }
            else

                // still in downtrend but new trough reached
                if( prcl[i] <= botprc )
                {
                    botprc = prcl[i];
                    botidx = i;
                    trailarraydn[i] = Min( trailarraydn[i - 1], res[i] );
                    trn[i] = 1;
                }
                // continuation inside downtrend
                else
                {
                    trailarraydn[i] = Min( trailarraydn[i - 1], res[i] );
                }
        }
}

line1 = Null;
lastidxpk = LastValue( ValueWhen( pk, bi ) );
lastidxtr = LastValue( ValueWhen( tr, bi ) );
lastvalpk = LastValue( ValueWhen( pk, prch ) );
lastvaltr = LastValue( ValueWhen( tr, prcl ) );
lastidxbuy = LastValue( ValueWhen( Buy, bi ) );
lastidxsell = LastValue( ValueWhen( Sell, bi ) );
valpk = LastValue( HighestSince( Ref( tr, -1 ), prch , 1 ) );
idxpk = LastValue( ValueWhen( prch == valpk, bi ) );
valtr = LastValue( LowestSince( Ref( pk, -1 ), prcl, 1 ) );
idxtr = LastValue( ValueWhen( prcl == valtr, bi ) );

if( lastidxsell > lastidxbuy AND lastidxsell > lastidxpk AND lastidxpk > lastidxtr )
{
    x0 = lastidxpk;
    y0 = lastvalpk;
    x1 = idxtr;
    y1 = valtr;
    line1 = linedn = LineArray( x0, y0, x1, y1 );
    tr[idxtr] = 1;
    valpk = LastValue( HighestSince( Ref( tr, -1 ), prch, 1 ) );
    idxpk = LastValue( ValueWhen( prch == valpk, bi ) );
}

if( lastidxsell < lastidxbuy AND lastidxbuy > lastidxtr AND lastidxpk < lastidxtr )
{
    x0 = lastidxtr;
    y0 = lastvaltr;
    x1 = idxpk;
    y1 = valpk;
    line1 = lineup = LineArray( x0, y0, x1, y1 );
    pk[idxpk] = 1;
    valtr = LastValue( LowestSince( Ref( pk, -1 ), prcl, 1 ) );
    idxtr = LastValue( ValueWhen( prcl == valtr, bi ) );
}

SetChartBkColor( ColorRGB( 0, 0, 0 ) );
xx = SparseCompress( pk OR tr, IIf( pk, prch, prcl ) );

Plot( xx, "", colorwhite, styleDots, Null, Null, 0, 1, 1 );

for( i = lvb; i > fvb; i-- )
{
    if( !IsEmpty( xx[i] ) AND xx[i] > xx[i - 1] )
    {
        GfxSelectSolidBrush( colorBlue );
        GfxRectangle( i - 0.5 , xx[i], i + 0.5, xx[i - 1] );
    }

    if( !IsEmpty( xx[i] ) AND xx[i] < xx[i - 1] )
    {
        GfxSelectSolidBrush( colorRed );
        GfxRectangle( i - 0.5 , xx[i], i + 0.5, xx[i - 1] );
    }
}

Title = "ATR VALUE: " + maatrper;


doesn't seem to repaint with this version. Only thing is that when you for instance switch from a 15min timeframe to a 30min timeframe the chart looks slightly different. This is also what is to be expected. Possibly I could resolve that. This is where the boxsize comes into play i think. Will play around with it when I have time

This one does not Repaint.

When I read through the code properly, it seems like it does not even use Reversal Box as param. Rather it uses ATR(period) to decide for Reversal. Isnt it?

Also wondering this is more of ZigZag approach to plot a PnF. Right?

What I am rather wondering the mathematic accuracy of this approach compared to the standard way of plotting PnF.

yes it does not use that but possibly I could build it in there (meaning the box size). Will look at it later.

I know very little about PnF but I think this way of handling data is from a time that people were getting their data from the newspaper. But I might be wrong. My approach is similar. The PnF uses for instance 3 box sizes to reverse the trend. My implementation uses a crossing of the price with the trail line. It is very similar imo

Possibly if I make the trail line jump in box sizes I will get the same chart as the PnF

Using box is the right way to solve it. Also the industry uses Percentage scaling where each box is a x% of closing price (user definable param).

So Y asix prices at exact x% distance.

All above comments are on the construction of PnF. If PnF is plotted using GFX then we can also draw a 45 Degree line from Anchor columns (Anchor column is where count of boxes are abv 15). All prices above this 45 Degree Trend line are pure momentum. Its such a fanicinating Approach to look at many efficient signals in one single construct.

PnF is an amazing Approach to scan for breakout and consolidation patterns.

Appreciate if you can please Implement these.

my code is just an idea how to take out the time factor from a traditional zigzag.

I just looked at what stockcharts does and they explain using percentage. See:
boxsize

I understand what you mean now. They say:

"Notice how the numerical value of each box changes. The difference between the top two boxes is 1.15 (116.52 - 115.37) and the difference between the bottom boxes is 0.84 (85.59 - 84.75). Despite different sizes in absolute numbers, the boxes are equal in percentage terms (1%). 1.15 is 1% of 115.37 and 0.84 is 1% of 84.75."

OK I understand now what you mean :slight_smile: If I can help not sure. I already have problems with Grahams code :grinning:. He uses a constant box size for the entire chart. The only problem is that this box size changes all the time when new data comes in. I will try and see if I can come up with a solution.

This picture shows a better picture of how the percentage Point and Figure chart works by applying to the TD chart. Here you can see that from September 19, 2024 to December 6, 2024, the stock dropped 20% from $64.02 to $52.47 - a total of 20 boxes down which represented a 20% drop. It is plotted based on close, which I tend not to prefer. I prefer High/Low plots. Now you have to be mindful when you are calculating the percentage drop, unlike going up, where it is just a simple task to take ($64 / $53) - 1 = 20% (which works out to be exactly 20 boxes), when you are going down, each successive percentage base becomes less and less, hence the total impact of the drop will be a bit lesser than 20% due to applying to lesser bases as it is dropping. Cheers, Merry Christmas.

1 Like

This one is plotted on black and white, which looks exactly like how it looks in the stockcharts.com. Each box up or down the y axis is exactly 1% increment of the previous box. For example, up at the top of the y axis, the first price is 70.72, then the next box is 70.02, so 70.72 is a 1% increment of 70.02, then 70.02 is a 1% increment of 69.33, and so on.

hi Clipper. Most of the code I found uses the concept that Graham Kavanagh uses and posted since around 2003. It is remarkable code although in some versions he makes some mistakes. Still it is remarkable code imo.

In the library there is a version by "mandeep" that improves Grahams code imo.

I however still did not add the "percentage box scaling". I will figure it out at some point but maybe you can explain the secret to me? Thanks.