IBD Distribution Days Study for TOS

R

RDMercer

Member
Anyone have a study that counts / calcs IBD distribution days for an index? Looking for something like:
((If todays close < (yesterdays close * .98) and (todays volume > yesterdays volume) (then distribution day)))
And if index rises 5% intraday then distribution day count resets to zero.

In a perfect study place a down arrow on a chert price bar marking the distribution day. AND a label box containing the running total count of distribution days over prior 25 days inclusive.

Open to all / any other enhancements or ideas. Thanks to all.
 
R

RDMercer

Member
I'm not too familiar with this. Maybe @markos can help?
It's William O'Neil's methodology to spot market turns and over a long time I've found it works very well. Of course right now we're one Tweet from a Twit away from either a YUGE rally or a sell-off depending on the mood of the moment. 🎱
 
markos

markos

Well-known member
VIP
@RDMercer I follow several former IBD port mgrs. The report below is from Ross Haber, who was there around David Ryan's time.
https://www.traderlion.com/top10/top-10-report-october-2-2019/According to people at the IBD Meet up event in town, they are still using Excel to keep the distribution count. We use TOS at the meeting but no one has tackled distribution count yet.
There may be someone at the IBD Meet up meeting in Seattle or Vancouver, I forget which, that may have it in a code that they might not mind sharing. You would have to hunt that down via Meet-up.com. The information regarding Seattle or Vancouver comes from a MarketSmith presenter. What you do have seems like a good start.
 
R

RDMercer

Member
@RDMercer I follow several former IBD port mgrs. The report below is from Ross Haber, who was there around David Ryan's time.
https://www.traderlion.com/top10/top-10-report-october-2-2019/According to people at the IBD Meet up event in town, they are still using Excel to keep the distribution count. We use TOS at the meeting but no one has tackled distribution count yet.
There may be someone at the IBD Meet up meeting in Seattle or Vancouver, I forget which, that may have it in a code that they might not mind sharing. You would have to hunt that down via Meet-up.com. The information regarding Seattle or Vancouver comes from a MarketSmith presenter. What you do have seems like a good start.
Thanks for the Report blog! I'm just looking to a lazy man's solution to the manual DD thing. If you ever run up on anything please advise. Rgds.
 
markos

markos

Well-known member
VIP
@RDMercer Your welcome. I sure will let you know. It's another one of my long-term searches....
 
U

UT2Pro1689

New member
I happen to start working on this study about a couple of months ago. I found we can get very close to the distribution day counts but not exactly the same as IBD.

There are more details disclosed by IBD than your brief description.
# Distribution day count tracking by IBD:
# https://www.investors.com/how-to-invest/investors-corner/tracking-distribution-days-a-crucial-habit/
# Stalling day tracking by IBD:
# https://www.investors.com/how-to-invest/investors-corner/can-slim-market-tops-stalling-distribution/

There are two major issues that we have to overcome:
1) The NYSE & NASDAQ volume numbers provided by TS are somewhat different from those provided by IBD. Any insights on how IBD gets or calculates their final volumes are be helpful.
2). IBD does not disclose the stalling day criteria fully and intends to keep it proprietary. Using the criteria mentioned in reference #2 above, I could see a lot of extra stalling days.

I'd be glad to share my script later after some additional calibration and cleaning up. It would be helpful if someone can provide the specific dates of distribution and stalling from IBD in the last few weeks so that I can improve the accuracy of the script.

Cheers
UT2Pro
 
markos

markos

Well-known member
VIP
@tomsk do you know of an IBD Style Distribution Day script? It's one of those eternal quests that some of us have been on. JQ doesn't have one in the Onenote.
 
T

tomsk

Well-known member
VIP
@markos Indeed there were several discussions on IBD related studies I have come across. However those were not based on the tickers comprising the IBD. Rather they focused more on RS of any tickers you wanted to track, so if AAPL was a component, you'll load that study and analyze the metrics the study detailed.

Here's one measure that Mobius posted several years ago. Hope this helps

Code:
declare lower;
plot IBDRS = close / close(symbol = "/ES");
EDIT: You can even get fancy and run a InertiaAll() call as well with some predefined starting date
 
U

UT2Pro1689

New member
@tomsk TOS has an RelativeStrength study included. I believe it fits IBD style.

TOS provides the open source as well:

#
# TD Ameritrade IP Company, Inc. (c) 2008-2019
#

declare lower;

input CorrelationWithSecurity = "SPX";
def close2 = close(CorrelationWithSecurity);

plot RS = if close2 == 0 then 0 else close/close2;
RS.setDefaultColor(GetColor(6));

def sr = CompoundValue("historical data" = RS, "visible data" = if isNaN(sr[1]) then RS else sr[1]);
plot SRatio = sr;
SRatio.setDefaultColor(GetColor(5));
 
B

Bamilus

New member
For the relative strength, I use Mansfield RS (see my post and code here https://usethinkscript.com/threads/mark-minervini-trend-template-tos-scanner.563/post-21314).

I found a collection of distribution days code that is based on IBD, but slightly edited based on the findings of Gil Morales and Chris Kacher (authors of "Trade Like an O'neil Disciple"). A man named Scott Johnson made the following, and I take no credit (links are to his Github which includes other indicators, as well, some of which I've found on here)

Possible Accumulation Day Indicator : This study shows a green bar arrow on chart for possible accumulation days (BASED ON PRICE ACTION ONLY). Read the note - the only Index that has volume in ToS, unfortunately, is $DJI. Using futures or ETF's is not a good proxy for SP500 or NASDAQ because their price moves aren't the exact same and the volume can be dramatically different. Right now I'm using $DJI since it has volume, but this study can be used for any index, you'll just have to get the volume from another source and confirm against that. There are tickers in TOS for total volume (such as $TVOLC/Q for NASDAQ COMPOSITE), but being able to translate that into a volume chart against the COMP symbol is beyond my ToS skills at the moment. If it's easy for anyone else, it would be cool to try to get volume matched with SP500 and NASDAQ index symbols in TOS.

Accumulation Distribution Bar Count - Does incorporate volume, so only works on $DJI. Displays a red bar for distribution days, green bar for accumulation. Yellow line is cumulative net accumulation days (sum accumulation days minus sum distribution days).

Accumulation Distribution Bar Count Breakout - Breakout in this context means: breakout of the factors in the study, not breakout in the sense of a price or stock breakout. This is same indicator as above, except it adds indicators to show not just the net accumulation days, but also total accumulation days, total distribution days, and the absolute number.

Accumulation Day Percent Gain Threshold - In the book "Trade Like an O'neil Disciple", they speak about how different market conditions require different cutoffs for accumulation days. This study shows the average minimum required % movement to be an accumulation day, based on some logic that you can evaluate yourself and based on last 200 days of index. I quite like this indicator, but there is an error in the last few lines of the code, so I deleted it but it still works. If anyone wants to see if they can fix it, it would be appreciate.

The line in question is:

Code:
AvgGainPlot.setHiding(if ShowAverageGainPlot == 1 then 0 else 1);
which errors out saying "ShowAverageGainPlot" isn't defined. I'm super new to ToS so I wasn't able to figure it out.

Anyways, hopes this helps.
 
N

Nick

Active member
2019 Donor
VIP
For the relative strength, I use Mansfield RS (see my post and code here https://usethinkscript.com/threads/mark-minervini-trend-template-tos-scanner.563/post-21314).

I found a collection of distribution days code that is based on IBD, but slightly edited based on the findings of Gil Morales and Chris Kacher (authors of "Trade Like an O'neil Disciple"). A man named Scott Johnson made the following, and I take no credit (links are to his Github which includes other indicators, as well, some of which I've found on here)

Possible Accumulation Day Indicator : This study shows a green bar arrow on chart for possible accumulation days (BASED ON PRICE ACTION ONLY). Read the note - the only Index that has volume in ToS, unfortunately, is $DJI. Using futures or ETF's is not a good proxy for SP500 or NASDAQ because their price moves aren't the exact same and the volume can be dramatically different. Right now I'm using $DJI since it has volume, but this study can be used for any index, you'll just have to get the volume from another source and confirm against that. There are tickers in TOS for total volume (such as $TVOLC/Q for NASDAQ COMPOSITE), but being able to translate that into a volume chart against the COMP symbol is beyond my ToS skills at the moment. If it's easy for anyone else, it would be cool to try to get volume matched with SP500 and NASDAQ index symbols in TOS.

Accumulation Distribution Bar Count - Does incorporate volume, so only works on $DJI. Displays a red bar for distribution days, green bar for accumulation. Yellow line is cumulative net accumulation days (sum accumulation days minus sum distribution days).

Accumulation Distribution Bar Count Breakout - Breakout in this context means: breakout of the factors in the study, not breakout in the sense of a price or stock breakout. This is same indicator as above, except it adds indicators to show not just the net accumulation days, but also total accumulation days, total distribution days, and the absolute number.

Accumulation Day Percent Gain Threshold - In the book "Trade Like an O'neil Disciple", they speak about how different market conditions require different cutoffs for accumulation days. This study shows the average minimum required % movement to be an accumulation day, based on some logic that you can evaluate yourself and based on last 200 days of index. I quite like this indicator, but there is an error in the last few lines of the code, so I deleted it but it still works. If anyone wants to see if they can fix it, it would be appreciate.

The line in question is:

Code:
AvgGainPlot.setHiding(if ShowAverageGainPlot == 1 then 0 else 1);
which errors out saying "ShowAverageGainPlot" isn't defined. I'm super new to ToS so I wasn't able to figure it out.

Anyways, hopes this helps.
Thanks for your sharing..will this check out and provide any feedbacks.
 
wtf_dude

wtf_dude

Active member
"The line in question is:

Code:
AvgGainPlot.setHiding(if ShowAverageGainPlot == 1 then 0 else 1);
which errors out saying "ShowAverageGainPlot" isn't defined. I'm super new to ToS so I wasn't able to figure it out. "



This is referencing a "show" choice for the average gain plot vs just showing the min pct

add this to the code up near the top under
declare lower;
input Period = 200;

####new line####
input ShowAverageGainPlot = yes;
 
Last edited:
U

UT2Pro1689

New member
For IBD, they use NYSE & NASDAQ volumes. They actually present temporary volumes first and then final volume numbers a certain time after daily close. It's not possible to get the final volume numbers in TOS as those at IBD. In my tests with TOS volume numbers, I found my distribution day counts sufficiently close. Most of days, it may be the same count as IBD, although not possible to be the same all the days.

The following is my unpolished code. It uses a lower pane and displays current distribution days (incl. stall days) and counts in the pane. I use the lower pane in my SPX & NASDAQ daily charts. Additionally, it draws a vertical line at the 2nd confirmation day based on user input (current market cycle: 2020-04-02. As of 2020/08/26, the distribution day count on SPX is 1). The code can definitely be improved a lot if someone is interested.

Note: IBD indicated the threshold for dismissing a distribution day after the index rise is 5% in a recent article for NASDAQ. The current code still uses 6% as the threshold.

Code:
#
# SMO_MktVolumesDaily.ts
# For daily charts only.
# Nasdaq total volume: $TVOL/Q ;   NYSE total volume: $TVOL
# Distribution day count tracking by IBD:
# https://www.investors.com/how-to-invest/investors-corner/
# tracking-distribution-days-a-crucial-habit/
# Stalling daysFromDate tracking by IBD:
# https://www.investors.com/how-to-invest/investors-corner/
# can-slim-market-tops-stalling-distribution/

declare lower;
declare zerobase;

def length = 20;        # volume moving average lenth in days

input volumeSymbol = {default NYSE, NASDAQ, SPX};

def volCl;
def volHi;
def findSymbol;

# To make volume differences more visible, use a base volume number
# The subtracted volume number is then magnified to present a bigger difference
def volMin;    # base number for volume

switch (volumeSymbol) {

# It was found there may be erratic volume data on close values
# On 2/19/2020, NYSE volume close values were 0 on 2/18 & 2/12
case NYSE:
    volCl = if close("$TVOL") == 0 then high("$TVOL") else close("$TVOL");
    #volCl = close("$TVOL");
    volHi = high("$TVOL");
# use SPX volume change percentage to replace erratic NYSE volume   
    findSymbol = if volCl == 0 then volCl[1] * (1+ (close("$TVOLSPC") - close("$TVOLSPC")[1])/close("$TVOLSPC")[1]) else volCl;
    volMin = 40000;
case NASDAQ:
    volcl = if close("$TVOL/Q") == 0 then high("$TVOL/Q") else close("$TVOL/Q");
    #volCl = close("$TVOL/Q");
    volHi = high("$TVOL/Q");
# use SPX volume change percentage to replace erratic NASDAQ volume   
    findSymbol = if volCl == 0 then volCl[1] * (1+ (close("$TVOLSPC") - close("$TVOLSPC")[1])/close("$TVOLSPC")[1]) else volCl;   
    volMin = 30000;
case SPX:
    volcl = if close("$TVOLSPC") == 0 then high("$TVOLSPC") else close("$TVOLSPC");    
    #volCl = close("$TVOLSPC");
    volHi = high("$TVOLSPC");
# use NYSE volume change percentage to replace erratic SPX volume   
    findSymbol = if volCl == 0 then volCl[1] * (1+ (close("$TVOL") - close("$TVOL")[1])/close("$TVOL")[1]) else volCl;
    volMin = 10000;

}

def cls = close;

def lastBar = HighestAll(if (IsNaN(cls), Double.NaN, BarNumber()));
def volumes = if IsNaN(findSymbol) and BarNumber() == lastBar then volumes[1] else findSymbol;

plot Vol = 3 * (volumes - volMin);
plot VolAvg = 3 * (Average(volumes, length) - volMin);

Vol.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
Vol.SetLineWeight(3);
Vol.DefineColor("Up", Color.UPTICK);
Vol.DefineColor("Down", Color.DOWNTICK);
Vol.AssignValueColor(if cls > cls[1] then Vol.color("Up")
                     else if cls < cls[1] then Vol.color("Down")
                     else GetColor(1));
VolAvg.SetDefaultColor(GetColor(8));

# Display useful texts starting at upper left corner
# End of Day volume change
def VolChangePercentDay = if (IsNaN(volumes[1]), 0,
                              100 * (volumes - volumes[1])/volumes[1]);

# InvalidDay was added since volume on 2019/11/29 (after Thanksgiving) was N/A.
addLabel( yes, if volChangePercentDay == 0 then "InvalidDay" else "" +
               "VolmChg="+ Concat("", round(VolChangePercentDay)) +
               "%", if VolChangePercentDay < 0 then
               Color.DARK_GRAY else if cls > cls[1] then Color.DARK_GREEN
               else Color.DARK_RED);

# Count distributionDay only if market price drops 0.2% or more
def downDay = cls <= (cls[1] * 0.998);

def volIncrease = volumes > volumes[1];

#
# After 25 sessions, a distribution day expires
# Use 25 bar numbers to represent 25 live sessions. GetDay or alike includes weekends.
#

def lastDays = if (BarNumber() > lastBar - 25) then 1 else 0;

# a distribution day can fall off the count if the index rises 6% or more,
# on an intraday basis, from its close on the day the higher-volume loss appears.
# Remove distribution days after prices increases 6% WHEN market is in uptrend.
# Need to fix:
# During market bottomed on 2-28-2020, stock price rose to 9.8% with market still in
# correction. The high volume selloff on 2-28 would still be counted as a distribution.
# The highest date should be after the distribution day

# Get proper high for future 25 days
def prHi = high;
def prLo = low;

def futureHigh = if isNaN(prHi[-25]) then futureHigh[1] else prHi[-25];
def prHighest = Highest(futureHigh, 25);
# Note: This condition disqualifies D-Days after large bear rally
# This is acceptable for now since D-Days in bear market are not really useful
def priceInRange = (cls * 1.06 >= prHighest);

def distributionDay = downDay and volIncrease and LastDays and priceInRange;

# Count valid distribution days in last 25 days
def distDayCount = sum(distributionDay, 25);


# A broad market correction makes the distribution day count irrelevent
# reset distribution count to 0
# Distribution day count should reset after 2nd confirmation day
# To do: automate the reset day when correction or follow-up day appears
# input distributionRstDay = 20191010;   a prior 2nd confirmation day
input distributionRstDay = 20200402;

def newDistributionCycle = GetYYYYMMDD() > distributionRstDay;
# Need to use above variable to restart d-day count
def newDistDays = sum(distributionDay and newDistributionCycle, 25);

# Display bubble red is count > 5, yellow >3, else while
AddChartBubble(distributionDay and !newDistributionCycle, vol, concat("", distDayCount),
                if distDayCount < 3 then color.WHITE
                else if distDayCount < 5 then color.LIGHT_ORANGE
                else color.RED);

# Show D-Day reset line at the next trading day after the reset date
# If D-Day is on Friday, then show it on next Monday
AddVerticalLine(if (GetDayOfWeek(distributionRstDay) < 5) and
                    (GetYYYYMMDD() == distributionRstDay + 1) then yes
                else if (GetDayOfWeek(distributionRstDay) == 5) and
                    (GetYYYYMMDD() == distributionRstDay + 3) then yes
                else no,
                "2ndCnfm", Color.GREEN, Curve.MEDIUM_DASH);

# to do: Comparison of preholiday data may be invalid.

#------------------------------------------------------------
# Stalling day counts
# 1. market has been rising and price is within 3% of 25 day high
# 2. Price making a high
#      current close >= prior 2 day close, or
#      current close >= prior day high
# 3. volume >= 95% of prior day volume
# 4. close in lower half of daily range
# 5. small gain within 0.4% for SPX & NASDAQ
# 6. The above IBD criteria disclosed in one article generates too many stalling days
#    Additional rules from IBD book are used to further reduce stalling counts
#    6.1 close up smaller than prior 2 days
#    6.2 low is lower than high of prior day (No unfilled gap-up)
#    6.3 there is at least one decent gain in prior 2 days
#    6.4 daily trading range should be similar to last 2 days
# 7. stalling counts are reduced due to time (25 days) and significantly upward
#    movement (6%) of the index
# Ex. 2019/11/12 was a stalling day on SPX, 2019/12/18 was stalling for Nasdaq

def priceIsHigh = cls >= cls[2] or cls >= prHi[1];
def priceLowHalf = cls < (prHi - prLo)/2 + prLo;
def priceGainSmall = cls - cls[1] > 0 and
                     ((cls - cls[1] < (cls[1] - cls[2])) or
                     ((cls - cls[1] < cls[2] - cls[3])));

# Added a 0.2% gap from prior day high to allow 2020/05/26 to count
# as a stalling day for NASDAQ
def priceGapFill = prLo < prHi[1] * 1.002;
def priceGainOk = (cls[1] - cls[2] > 0.002 * cls[2]) or
                   (cls[2] - cls[3] > 0.002 * cls[3]);
# price trading range is the high - low plus the gapup if any
def priceRange = if prLo > prHi[1] then prHi-prHi[1] else prHi -prLo;
def priceRangeBig = priceGainOk and priceRange > 0.8 * min(priceRange[1], priceRange[2]);

def stallDay = cls - cls[25] > 0 and
               cls >= 0.97 * Highest( prHi, 25) and
               volumes > 0.95 * volumes[1] and
               cls - cls[1] > 0 and
               cls - cls[1] < 1.004 * cls[1] and
               priceIsHigh and priceLowHalf and priceGainSmall and priceGapFill and
               priceRangeBig and lastDays;

# Count stalling days
def stallDayCount = sum(stallDay, 25);


# calculate new stalling days after the reset day (e.g. follow-up date)
def newStallDays = sum(StallDay and newDistributionCycle, 25);

# Display final distribution count (incl. stall days)
# red if >= 5, >3: yellow, else green
def totalDdays = distDayCount+stallDayCount;
def totalNdDays = newDistDays+StallDayCount;

AddChartBubble(distributionDay and newDistributionCycle, vol,
                if volCl == 0 then concat("?", newDistDays) else concat("", newDistDays),
                if totalNdDays < 3 then color.WHITE
                else if totalNdDays < 5 then color.LIGHT_ORANGE
                else color.RED);

AddChartBubble(stallDay AND lastDays, vol,
                if volCl == 0 then "?S" + concat("", stallDayCount)
                              else "S" + concat("", stallDayCount),
                if totalNdDays < 3 then color.WHITE
                else if totalNdDays < 5 then color.LIGHT_ORANGE
                else color.RED);
AddChartBubble(volCl == 0 AND !stallDay AND !distributionDay, vol, "?", color.LIGHT_GRAY);

addLabel(totalDdays != totalNdDays, "AllDdays =" + concat("", totalDdays), Color.GRAY);
addLabel(yes, "NewDdays =" + concat("",totalNdDays ),
         if totalNdDays <=2 then Color.Green
         else if totalNdDays <= 4 then Color.ORANGE
         else Color.RED);

# To do:
# IBD's book: "The Successful Investor"
#    If most of the 3 to 5 days of distributions have small spreads from high to low
#    the distribution is not large enough to cause market turning down.
#    Significant distributions should have the spreads a little wider than average.
#
 
wtf_dude

wtf_dude

Active member
For IBD, they use NYSE & NASDAQ volumes. They actually present temporary volumes first and then final volume numbers a certain time after daily close. It's not possible to get the final volume numbers in TOS as those at IBD. In my tests with TOS volume numbers, I found my distribution day counts sufficiently close. Most of days, it may be the same count as IBD, although not possible to be the same all the days.

The following is my unpolished code. It uses a lower pane and displays current distribution days (incl. stall days) and counts in the pane. I use the lower pane in my SPX & NASDAQ daily charts. Additionally, it draws a vertical line at the 2nd confirmation day based on user input (current market cycle: 2020-04-02. As of 2020/08/26, the distribution day count on SPX is 1). The code can definitely be improved a lot if someone is interested.

Note: IBD indicated the threshold for dismissing a distribution day after the index rise is 5% in a recent article for NASDAQ. The current code still uses 6% as the threshold.

Code:
#
# SMO_MktVolumesDaily.ts
# For daily charts only.
# Nasdaq total volume: $TVOL/Q ;   NYSE total volume: $TVOL
# Distribution day count tracking by IBD:
# https://www.investors.com/how-to-invest/investors-corner/
# tracking-distribution-days-a-crucial-habit/
# Stalling daysFromDate tracking by IBD:
# https://www.investors.com/how-to-invest/investors-corner/
# can-slim-market-tops-stalling-distribution/

declare lower;
declare zerobase;

def length = 20;        # volume moving average lenth in days

input volumeSymbol = {default NYSE, NASDAQ, SPX};

def volCl;
def volHi;
def findSymbol;

# To make volume differences more visible, use a base volume number
# The subtracted volume number is then magnified to present a bigger difference
def volMin;    # base number for volume

switch (volumeSymbol) {

# It was found there may be erratic volume data on close values
# On 2/19/2020, NYSE volume close values were 0 on 2/18 & 2/12
case NYSE:
    volCl = if close("$TVOL") == 0 then high("$TVOL") else close("$TVOL");
    #volCl = close("$TVOL");
    volHi = high("$TVOL");
# use SPX volume change percentage to replace erratic NYSE volume  
    findSymbol = if volCl == 0 then volCl[1] * (1+ (close("$TVOLSPC") - close("$TVOLSPC")[1])/close("$TVOLSPC")[1]) else volCl;
    volMin = 40000;
case NASDAQ:
    volcl = if close("$TVOL/Q") == 0 then high("$TVOL/Q") else close("$TVOL/Q");
    #volCl = close("$TVOL/Q");
    volHi = high("$TVOL/Q");
# use SPX volume change percentage to replace erratic NASDAQ volume  
    findSymbol = if volCl == 0 then volCl[1] * (1+ (close("$TVOLSPC") - close("$TVOLSPC")[1])/close("$TVOLSPC")[1]) else volCl;  
    volMin = 30000;
case SPX:
    volcl = if close("$TVOLSPC") == 0 then high("$TVOLSPC") else close("$TVOLSPC");   
    #volCl = close("$TVOLSPC");
    volHi = high("$TVOLSPC");
# use NYSE volume change percentage to replace erratic SPX volume  
    findSymbol = if volCl == 0 then volCl[1] * (1+ (close("$TVOL") - close("$TVOL")[1])/close("$TVOL")[1]) else volCl;
    volMin = 10000;

}

def cls = close;

def lastBar = HighestAll(if (IsNaN(cls), Double.NaN, BarNumber()));
def volumes = if IsNaN(findSymbol) and BarNumber() == lastBar then volumes[1] else findSymbol;

plot Vol = 3 * (volumes - volMin);
plot VolAvg = 3 * (Average(volumes, length) - volMin);

Vol.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
Vol.SetLineWeight(3);
Vol.DefineColor("Up", Color.UPTICK);
Vol.DefineColor("Down", Color.DOWNTICK);
Vol.AssignValueColor(if cls > cls[1] then Vol.color("Up")
                     else if cls < cls[1] then Vol.color("Down")
                     else GetColor(1));
VolAvg.SetDefaultColor(GetColor(8));

# Display useful texts starting at upper left corner
# End of Day volume change
def VolChangePercentDay = if (IsNaN(volumes[1]), 0,
                              100 * (volumes - volumes[1])/volumes[1]);

# InvalidDay was added since volume on 2019/11/29 (after Thanksgiving) was N/A.
addLabel( yes, if volChangePercentDay == 0 then "InvalidDay" else "" +
               "VolmChg="+ Concat("", round(VolChangePercentDay)) +
               "%", if VolChangePercentDay < 0 then
               Color.DARK_GRAY else if cls > cls[1] then Color.DARK_GREEN
               else Color.DARK_RED);

# Count distributionDay only if market price drops 0.2% or more
def downDay = cls <= (cls[1] * 0.998);

def volIncrease = volumes > volumes[1];

#
# After 25 sessions, a distribution day expires
# Use 25 bar numbers to represent 25 live sessions. GetDay or alike includes weekends.
#

def lastDays = if (BarNumber() > lastBar - 25) then 1 else 0;

# a distribution day can fall off the count if the index rises 6% or more,
# on an intraday basis, from its close on the day the higher-volume loss appears.
# Remove distribution days after prices increases 6% WHEN market is in uptrend.
# Need to fix:
# During market bottomed on 2-28-2020, stock price rose to 9.8% with market still in
# correction. The high volume selloff on 2-28 would still be counted as a distribution.
# The highest date should be after the distribution day

# Get proper high for future 25 days
def prHi = high;
def prLo = low;

def futureHigh = if isNaN(prHi[-25]) then futureHigh[1] else prHi[-25];
def prHighest = Highest(futureHigh, 25);
# Note: This condition disqualifies D-Days after large bear rally
# This is acceptable for now since D-Days in bear market are not really useful
def priceInRange = (cls * 1.06 >= prHighest);

def distributionDay = downDay and volIncrease and LastDays and priceInRange;

# Count valid distribution days in last 25 days
def distDayCount = sum(distributionDay, 25);


# A broad market correction makes the distribution day count irrelevent
# reset distribution count to 0
# Distribution day count should reset after 2nd confirmation day
# To do: automate the reset day when correction or follow-up day appears
# input distributionRstDay = 20191010;   a prior 2nd confirmation day
input distributionRstDay = 20200402;

def newDistributionCycle = GetYYYYMMDD() > distributionRstDay;
# Need to use above variable to restart d-day count
def newDistDays = sum(distributionDay and newDistributionCycle, 25);

# Display bubble red is count > 5, yellow >3, else while
AddChartBubble(distributionDay and !newDistributionCycle, vol, concat("", distDayCount),
                if distDayCount < 3 then color.WHITE
                else if distDayCount < 5 then color.LIGHT_ORANGE
                else color.RED);

# Show D-Day reset line at the next trading day after the reset date
# If D-Day is on Friday, then show it on next Monday
AddVerticalLine(if (GetDayOfWeek(distributionRstDay) < 5) and
                    (GetYYYYMMDD() == distributionRstDay + 1) then yes
                else if (GetDayOfWeek(distributionRstDay) == 5) and
                    (GetYYYYMMDD() == distributionRstDay + 3) then yes
                else no,
                "2ndCnfm", Color.GREEN, Curve.MEDIUM_DASH);

# to do: Comparison of preholiday data may be invalid.

#------------------------------------------------------------
# Stalling day counts
# 1. market has been rising and price is within 3% of 25 day high
# 2. Price making a high
#      current close >= prior 2 day close, or
#      current close >= prior day high
# 3. volume >= 95% of prior day volume
# 4. close in lower half of daily range
# 5. small gain within 0.4% for SPX & NASDAQ
# 6. The above IBD criteria disclosed in one article generates too many stalling days
#    Additional rules from IBD book are used to further reduce stalling counts
#    6.1 close up smaller than prior 2 days
#    6.2 low is lower than high of prior day (No unfilled gap-up)
#    6.3 there is at least one decent gain in prior 2 days
#    6.4 daily trading range should be similar to last 2 days
# 7. stalling counts are reduced due to time (25 days) and significantly upward
#    movement (6%) of the index
# Ex. 2019/11/12 was a stalling day on SPX, 2019/12/18 was stalling for Nasdaq

def priceIsHigh = cls >= cls[2] or cls >= prHi[1];
def priceLowHalf = cls < (prHi - prLo)/2 + prLo;
def priceGainSmall = cls - cls[1] > 0 and
                     ((cls - cls[1] < (cls[1] - cls[2])) or
                     ((cls - cls[1] < cls[2] - cls[3])));

# Added a 0.2% gap from prior day high to allow 2020/05/26 to count
# as a stalling day for NASDAQ
def priceGapFill = prLo < prHi[1] * 1.002;
def priceGainOk = (cls[1] - cls[2] > 0.002 * cls[2]) or
                   (cls[2] - cls[3] > 0.002 * cls[3]);
# price trading range is the high - low plus the gapup if any
def priceRange = if prLo > prHi[1] then prHi-prHi[1] else prHi -prLo;
def priceRangeBig = priceGainOk and priceRange > 0.8 * min(priceRange[1], priceRange[2]);

def stallDay = cls - cls[25] > 0 and
               cls >= 0.97 * Highest( prHi, 25) and
               volumes > 0.95 * volumes[1] and
               cls - cls[1] > 0 and
               cls - cls[1] < 1.004 * cls[1] and
               priceIsHigh and priceLowHalf and priceGainSmall and priceGapFill and
               priceRangeBig and lastDays;

# Count stalling days
def stallDayCount = sum(stallDay, 25);


# calculate new stalling days after the reset day (e.g. follow-up date)
def newStallDays = sum(StallDay and newDistributionCycle, 25);

# Display final distribution count (incl. stall days)
# red if >= 5, >3: yellow, else green
def totalDdays = distDayCount+stallDayCount;
def totalNdDays = newDistDays+StallDayCount;

AddChartBubble(distributionDay and newDistributionCycle, vol,
                if volCl == 0 then concat("?", newDistDays) else concat("", newDistDays),
                if totalNdDays < 3 then color.WHITE
                else if totalNdDays < 5 then color.LIGHT_ORANGE
                else color.RED);

AddChartBubble(stallDay AND lastDays, vol,
                if volCl == 0 then "?S" + concat("", stallDayCount)
                              else "S" + concat("", stallDayCount),
                if totalNdDays < 3 then color.WHITE
                else if totalNdDays < 5 then color.LIGHT_ORANGE
                else color.RED);
AddChartBubble(volCl == 0 AND !stallDay AND !distributionDay, vol, "?", color.LIGHT_GRAY);

addLabel(totalDdays != totalNdDays, "AllDdays =" + concat("", totalDdays), Color.GRAY);
addLabel(yes, "NewDdays =" + concat("",totalNdDays ),
         if totalNdDays <=2 then Color.Green
         else if totalNdDays <= 4 then Color.ORANGE
         else Color.RED);

# To do:
# IBD's book: "The Successful Investor"
#    If most of the 3 to 5 days of distributions have small spreads from high to low
#    the distribution is not large enough to cause market turning down.
#    Significant distributions should have the spreads a little wider than average.
#
Like the look of this and I'll see if there are any mods worth making. ll have to go back and reread the ibd info on this stuff. So the date automatically resets or does it have to be changed manually after a market dump? I can't tell between the notes and the code itself
 
wtf_dude

wtf_dude

Active member
I've never programmed with those vertical lines, but I would love to see this used as an upper and just have the stalls and the Ddays as labelled vertical lines just like you have with that 2nd conf line. The new days and volume change labels transfer just fine already.
 
U

UT2Pro1689

New member
I think you can make it to the upper chart with a few limited mods. The script does not calculate 2nd confirmation day. User has to enter it. It will restart the D day counts. BTW, it detected a stall day on SPX and a distribution day on Nasdaq for yesterday.
 
wtf_dude

wtf_dude

Active member
I think you can make it to the upper chart with a few limited mods. The script does not calculate 2nd confirmation day. User has to enter it. It will restart the D day counts. BTW, it detected a stall day on SPX and a distribution day on Nasdaq for yesterday.
yea i noticed that day. I'll try and tweak it this week and see how it looks at the upper. Good work man!

edit: I want to see if i can get it looking like this one if you've never seen it. Gives you different variants of breadth thrusts for new bulls.

Code:
#
# Breakaway Momentum (Breadth Thrust)
# defined: 10d sma advances > 1.97 x 10d sma declines.
# article: https://www.walterdeemer.com/bam.htm
#
# Desmond Reversals
# defined: 1 or more 90% downvolume day(s) [panic selling] followed by 90% upvolume day or 2 80+% upvolume days [panic buying]
# article: https://data.bloomberglp.com/assets/sites/2/2014/01/Desmond-report.pdf
#
# Whaley Thrusts
# defined:
#    - breath thursts (ADT): 5day cumulative advance/total issues crossing above 75 [whaley1] or crossing below 25 [whaley2]
#    - volume thrusts (UDT): 5day cumulative upvolume/total volume crossing above 77.8 [whaley3] or crossing below 16.4 [whaley4]
#    - sprice thrusts (SPT): 5day cumulative SP500 price % change of +10.05% [whaley5] or -13.85% [whaley6]
# article: http://docs.mta.org/pdfs/dowaward-2010.pdf
#
# Zweig Thrust
# defined: 10d ema advances crosses above 4.0 and 6.1 within 10 trading days
# article: https://www.seeitmarket.com/did-zweig-breadth-thrust-indicator-flash-buy-signal-for-stock-market/
# sourcecode: @ClarenceCarr
#


declare upper;

input thrustType = {default "Breakaway", "Desmond", "Whaley|ADT", "Whaley|UDT", "Whaley|SPT", "Zweig"};
input exchange = {default NYSE, NASDAQ, SnP500, Nasdaq100, Russell2000, Dow30, AMEX};
input signalType = {"Off", default "Bubble", "VerticalLine"};
input signalLabel = {"Date", "Type", default "Type|Date"};
input showLabels = no;
input showClouds = yes;
input tradeDays = 60;
input showExtLine = yes;


def advanceI;
def declineI;
def advanceV;
def declineV;
switch (exchange) {
case NYSE:
    advanceI = close("$ADVN");
    declineI = close("$DECN");
    advanceV = close("$UVOL");
    declineV = close("$DVOL");
case NASDAQ:
    advanceI = close("$ADVN/Q");
    declineI = close("$DECN/Q");
    advanceV = close("$UVOL/Q");
    declineV = close("$DVOL/Q");
case SnP500:
    advanceI = close("$ADVSP");
    declineI = close("$DECLSP");
    advanceV = close("$UVOLSP");
    declineV = close("$DVOLSP");
case Nasdaq100:
    advanceI = close("$ADVND");
    declineI = close("$DECLND");
    advanceV = close("$UVOLND");
    declineV = close("$DVOLND");
case Russell2000:
    advanceI = close("$ADVRL");
    declineI = close("$DECLRL");
    advanceV = close("$UVOLRL");
    declineV = close("$DVOLRL");
case Dow30:
    advanceI = close("$ADVI");
    declineI = close("$DECLI");
    advanceV = close("$UVOLI");
    declineV = close("$DVOLI");
case AMEX:
    advanceI = close("$ADVA");
    declineI = close("$DECA");
    advanceV = close("$UVOLUS");
    declineV = close("$DVOLUS");
}

def aCount = if isnan(advanceI) then aCount[1] + 1 else 0;
def dCount = if isnan(declineI) then dCount[1] + 1 else 0;
def addI = if isnan(advanceI) then GetValue(advanceI, aCount) else advanceI;
def decI = if isnan(declineI) then GetValue(declineI, dCount) else declineI;
def totI = addI+decI;
def addIR = addI/totI;
def decIR = decI/totI;

def aVCount = if isnan(advanceV) then aVCount[1] + 1 else 0;
def dVCount = if isnan(declineV) then dVCount[1] + 1 else 0;
def addV = if isnan(advanceV) then GetValue(advanceV, aVCount) else advanceV;
def decV = if isnan(declineV) then GetValue(declineV, dVCount) else declineV;
def totV = addV + decV;
def addVR = addV/totV;
def decVR = decV/totV;

def SPChg = 100 * (close("SPX") / close("SPX")[1] - 1);


def advnDecl;
def mean;
def weak;
def thrust;
switch (thrustType){
case "Breakaway":
    advnDecl = round(average(addIR,10) - 1.97*average(decIR,10),2);
    mean = 0;
    weak = Double.NaN;
    thrust = Double.NaN;
case "Desmond":
    advnDecl = if addVR > decVR then round(addVR*100,2) else round(-decVR*100,2);
    mean = 0;
    weak = -80;
    thrust = 80;
case "Whaley|ADT":
    advnDecl = round(sum(addI,5)/sum(totI,5),3)*100;
    mean = 50;
    weak = 25;
    thrust = 75;
case "Whaley|UDT":
    advnDecl = round(sum(addV,5)/sum(totV,5),3)*100;
    mean = 50;
    weak = 16.4;
    thrust = 77.8;
case "Whaley|SPT":
    advnDecl = round(sum(SPChg,5),2);
    mean = 0;
    weak = -13.85;
    thrust = 10.05;
case "Zweig":
    advnDecl = round(ExpAverage(addIR,10),3);
    mean = 0.5;
    weak = 0.4;
    thrust = 0.61;
}

# -- plots
def ADExpAvg = if IsNaN(close) then double.nan else if IsNaN(advnDecl) then mean else advnDecl;


# -- signals
# breakaway signal
def breakaway = average(addIR,10) crosses above 1.97*average(decIR,10);
def breakaway2 = average(addIR,20) crosses above 1.72*average(decIR,20); # jan 2019 addendum
# desmond
def desmond1 = advnDecl[1]<=-90 and advnDecl>=90;
def desmond2 = advnDecl[2]<=-90 and advnDecl[1]>=80 and advnDecl>=80;
# whaley signal
def whaley1 = advnDecl crosses above thrust; #whaley1,3,&5
def whaley2 = advnDecl crosses below weak; #whaley2,4,&6
# zweig signal
def maxDays = 10;
def zCount = if advnDecl >= Weak then zCount[1] + 1 else 0;
def zweig = advnDecl crosses above Thrust and zCount <= maxDays;

def signal =
if thrustType == thrustType."breakaway" then (breakaway or breakaway2) else
if thrustType == thrustType."desmond" then (desmond1 or desmond2) else
if thrustType == thrustType."whaley|ADT" then (whaley1 or whaley2) else
if thrustType == thrustType."whaley|UDT" then (whaley1 or whaley2) else
if thrustType == thrustType."whaley|SPT" then (whaley1 or whaley2) else
if thrustType == thrustType."Zweig" then Zweig else
double.nan;

AddVerticalLine(signalType == signalType."VerticalLine" and signal,
(if signalLabel != signalLabel."Date" then
    " " + thrustType + ", " else ", ")
+
 (if GetMonth()==1 then " Jan " else
 if GetMonth()==2 then " Feb " else
 if GetMonth()==3 then " Mar " else
 if GetMonth()==4 then " Apr " else
 if GetMonth()==5 then " May " else
 if GetMonth()==6 then " Jun " else
 if GetMonth()==7 then " Jul " else
 if GetMonth()==8 then " Aug " else
 if GetMonth()==9 then " Sep " else
 if GetMonth()==10 then " Oct " else
 if GetMonth()==11 then " Nov " else
 " Dec ")
+ getdayofmonth(getyyyymmdd()) + ", " + asprice(getyear())
+ (if thrustType == thrustType."Zweig" then " (" + zCount + " days)" else " ")
, globalcolor("signal"), 1);

Addchartbubble(signalType == signalType."Bubble" and signalLabel != signalLabel."Date" and signal, low, thrustType + (if breakaway2 then "2" else ""), globalcolor("signal"), 0);
Addchartbubble(signalType == signalType."Bubble" and signalLabel != signalLabel."Type" and signal, low,
 (if GetMonth()==1 then "Jan " else
 if GetMonth()==2 then "Feb " else
 if GetMonth()==3 then "Mar " else
 if GetMonth()==4 then "Apr " else
 if GetMonth()==5 then "May " else
 if GetMonth()==6 then "Jun " else
 if GetMonth()==7 then "Jul " else
 if GetMonth()==8 then "Aug " else
 if GetMonth()==9 then "Sep " else
 if GetMonth()==10 then "Oct " else
 if GetMonth()==11 then "Nov " else
 "Dec ")
+ getdayofmonth(getyyyymmdd()) + ", " + asprice(getyear())
, color.light_gray, 0);


# -- clouds
def cond1 = if isnan(close) then double.nan else if signal or signal[1] then double.positive_INFINITY else Double.NaN;
def cond2 = if !showClouds then double.nan else double.negative_infinity;
AddCloud(cond1, cond2, globalcolor("signal"));
# zweig signal window
def ccross = advnDecl crosses above weak;
def ccloud = Sum(ccross, maxdays);
def value3 = if ccloud then double.positive_INFINITY else Double.NaN;
#AddCloud(if thrustType != thrustType."Zweig" then double.nan else value3, value2, color.light_gray);

# -- trade window
# sourcecode: @Pipsinger, bacontrading.com
Rec SignalBar = if barnumber() == 1 then double.nan else CompoundValue(1,if Signal then 0 else SignalBar[1] + 1,0);
def cond3 = if SignalBar <= tradeDays then double.positive_INFINITY else double.nan;
AddCloud(cond3, cond2, color.light_gray);


# -- thrust candles
# sourcecode: https://funwiththinkscript.com/adding-space-between-candles/
def o = open;
def h = high;
def l = low;
def c = close;

# up candles
def UpO;
def UpH;
def UpL;
def UpC;
if o <= c and signal
then {
    UpO = o;
    UpH = h;
    UpL = l;
    UpC = c;
} else {
    UpO = Double.NaN;
    UpH = Double.NaN;
    UpL = Double.NaN;
    UpC = Double.NaN;
}
AddChart(high = UpH, low = UpL, open = UpC, close = UpO, type = ChartType.CANDLE, growcolor = globalcolor("signal"));
AddChart(high = UpH, low = UpL, open = UpO, close = UpC, type = ChartType.CANDLE, growcolor = globalcolor("candlebody"));

# down candles
def DnO;
def DnH;
def DnL;
def DnC;
if o > c and signal
then {
    DnO = o;
    DnH = h;
    DnL = l;
    DnC = c;
} else {
    DnO = Double.NaN;
    DnH = Double.NaN;
    DnL = Double.NaN;
    DnC = Double.NaN;
}
AddChart(high = DnH, low = DnL, open = DnO, close = DnC, type = ChartType.CANDLE, growcolor = globalcolor("signal"));
AddChart(high = DnH, low = DnL, open = DnC, close = DnO, type = ChartType.CANDLE, growcolor = globalcolor("candlebody"));


# -- colors
defineglobalcolor("average", CreateColor(8,65,93)); #darker blue
#defineglobalcolor("average", CreateColor(0,102,153)); #blue
defineglobalcolor("candlebody", createcolor(25,25,25));
defineglobalcolor("signal", color.dark_orange);
defineglobalcolor("extLine", color.light_gray);


# -- labels
AddLabel(showLabels, " " + thrustType + " ", globalcolor("average"));
AddLabel(showLabels, " " + (if thrustType == thrustType."whaley|SPT" then "SnP500" else exchange) + " ", globalcolor("average"));

# breakaway labels
def ADRI =
if addI == 0 then 0 else
if decI == 0 then 0 else
if addI > decI then round(addI/decI,1) else round(-decI/addI, 1);
def ADRV =
if addV == 0 then 0 else
if decV == 0 then 0 else
if addV > decV then round(addV/decV,1) else round(-decV/addV, 1);

AddLabel(showLabels and thrustType == thrustType."breakaway",
if ADRI == 0 then " Iss: TILT " else
" Iss: " + ADRI + ":1 ", if addIR > decIR then color.uptick else if addIR < decIR then color.downtick else color.gray);
AddLabel(showLabels and thrustType == thrustType."breakaway", " " + round(addIR,2)*100 + "% ", if addIR > decIR then color.uptick else color.gray);
AddLabel(showLabels and thrustType == thrustType."breakaway", " " + round(decIR,2)*100 + "% ", if addIR > decIR then color.gray else color.downtick);

# desmond labels
AddLabel(showLabels and thrustType == thrustType."desmond",
if ADRV == 0 then " Vol: TILT " else
" Vol: " + ADRV + ":1 ", if addVR > decVR then color.uptick else if addVR < decVR then color.downtick else color.gray);
AddLabel(showLabels and thrustType == thrustType."desmond", " " + round(addVR,2)*100 + "% ", if addVR > decVR then color.uptick else color.gray);
AddLabel(showLabels and thrustType == thrustType."desmond", " " + round(decVR,2)*100 + "% ", if addVR > decVR then color.gray else color.downtick);

# whaley labels
AddLabel(showLabels and thrustType == thrustType."whaley|ADT", " " + ADExpAvg + "% ", if signal then globalcolor("signal") else globalcolor("average"));
AddLabel(showLabels and thrustType == thrustType."whaley|UDT", " " + ADExpAvg + "% ", if signal then globalcolor("signal") else globalcolor("average"));
AddLabel(showLabels and thrustType == thrustType."whaley|SPT", " " + ADExpAvg + "% Chg ", if signal then globalcolor("signal") else globalcolor("average"));

# zweig labels
AddLabel(showLabels and thrustType == thrustType."Zweig", " " + ADExpAvg * 100 + "% stocks ", if signal then globalcolor("signal") else if adexpavg crosses above Weak then color.magenta else globalcolor("average"));
AddLabel(showLabels and thrustType == thrustType."Zweig" and zCount <= maxDays, " AD >= Weak: " + zcount + " days ", color.light_gray);
#AddLabel(showLabels and thrustType == thrustType."Zweig" and zCount <= maxDays, " Signal Window: " + (10-zCount) + (if (10-zCount)<=1 then " day left" else " days "), if (10-zCount)<=2 then color.downtick else color.light_gray);
AddLabel(showLabels and thrustType == thrustType."Zweig" and zweig, " Thrust ", globalcolor("signal"));

# signal label
AddLabel(signal, " Alert ", globalcolor("signal"));


# -- extLine
def barNumber = BarNumber();
def barCount = HighestAll(If(IsNaN(close), 0, barNumber));
def colorAvg = if isnan(close) then colorAvg[1] else if signal then 1 else 0;

def extend = if barNumber == 1 then Double.NaN else if barNumber == barCount then close
else if barNumber == barCount then Double.NaN else extend[1];
plot extLine = extend;
extLine.SetStyle(Curve.SHORT_DASH);
extLine.AssignValueColor(if colorAvg == 1 then globalcolor("signal") else globalcolor("extLine"));
#extLine.HideBubble();
extLine.HideTitle();
extLine.SetHiding(!showExtLine);
 
wtf_dude

wtf_dude

Active member
I think you can make it to the upper chart with a few limited mods. The script does not calculate 2nd confirmation day. User has to enter it. It will restart the D day counts. BTW, it detected a stall day on SPX and a distribution day on Nasdaq for yesterday.
Dude, just wanted to let you know I'm loving this. I haven't dug too much into the math to check it, but it looks great so far (probably a new favorite for risk management) I'm eschewing the idea of making it an upper display just because the number of dist days would end up cluttering the chart either way. Good stuff
 

Similar threads

Top