@wtf_dude hey boss, was hoping if you had the chance to convert the IBD script on upper display. It would be very interesting to see how that turns out. Also thinking if you created any scanner with the R and the distribution days. Please share. Thanksyea 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);
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
Something like this: https://usethinkscript.com/threads/ibd-distribution-days-study-for-tos.748/page-3#post-53686 will get you started. Just substitute your symbolHi I am looking to create a study using the NASDAQ composite volume difference between advancing - declining stocks. Is this shown in $VOLQDC? It would be great to see it in a histogram with a zero line if possible. Does anyone have a study of this already or knows how to create one? Thanks!
hell yea, thanks for the info!A few years ago, I found an Excel spreadsheet on an IBD MeetUp page that tracked the performance of the major market indexes during each of the 3 market conditions: confirmed uptrend, uptrend under pressure and Market in correction listed in the Market Pulse box in IBD. I downloaded the spreadsheet and continued tracking the Market Pulse calls for a few more years. After collecting over 5 years of data, I found out that the market indexes performed best during the Market in Correction condition. Confirmed Uptrend was a distant second and Uptrend under pressure was last. The NASDAQ actually performed a whopping 79% better during Market in Correction than during Confirmed Uptrend! I also saw a study that someone did where they tracked the Market Pulse calls during the 2007-2009 bear market. If memory serves, there were about 17 follow-through days confirming an uptrend during that massive decline. Many of the follow-through days occurred at the top of a bear market rally. Needless to say, I no longer use the IBD Market Pulse to time the market.
@UT2Pro1689 I just stumbled across this indicator. In this comment, you mentioned you changed 2 things. One was to make the R1 days "repaint", and the other was to correct the calculation for R1 day. I'd like to keep the proper calculation for R1 days, but not have the indicator show if the attempted rally fails. Example: it's showing today as a distribution day for SPX, which I believe is supposed to nullify the R1 day from 1/24/22, but the R1 bubble is still showing. Can you help me with what parts I'd need to adjust?The 1st rally day signal in the script is intended to show the final and valid signal only, among many attempted rallies during a market correction. So any failed attempted R1 day will disappear from the chart. The code has to be modified if the failed R1 day needs to be shown.
On the other hand, I fixed a bug which caused today as the R1 day on Nasdaq chart. The actual R1 day should be March 5.
Here's the updated section of the code. I kept the old code in comments for references.
Code:# market correction is currently defined as down 8% from top # need to be refined so that it will work in a bear market that is forming a bottom. # In this case, the 8% drop may not be required. def mktCr = prLo[1] <= highest(high, 25) * .92; def prRng = TrueRange(prHi, cls, prLo); #prHi - prLo; #def pinkRday = cls > (prLo + (prHi - prLo)/2) and cls < cls[1] and # prLo <= lowest(prLo[1],rDayInterval) and # prLo <= lowest(futureLow, rDayInterval); def pinkRday = cls > (prLo + prRng/2) and cls < cls[1] and prLo <= lowest(prLo[1],rDayInterval) and prLo <= lowest(futureLow, rDayInterval); #def realRday = cls > cls[1] and # cls <= lowest(futureCls, rDayInterval) and # cls[1] <= lowest(cls[1], rDayInterval) and # sum(pinkRday[1], rDayInterval) == 0; # The real rally day has its close higher than prior close # A rally day is invalidated if the low is broken in subsequent days def realRday = cls > cls[1] and prLo <= lowest(futureLow, rDayInterval) and sum(realRday[1], rDayInterval) == 0 and sum(pinkRday[1], rDayInterval) == 0;
The updated chart should look like the following image.
@rh19@UT2Pro1689 I just stumbled across this indicator. In this comment, you mentioned you changed 2 things. One was to make the R1 days "repaint", and the other was to correct the calculation for R1 day. I'd like to keep the proper calculation for R1 days, but not have the indicator show if the attempted rally fails. Example: it's showing today as a distribution day for SPX, which I believe is supposed to nullify the R1 day from 1/24/22, but the R1 bubble is still showing. Can you help me with what parts I'd need to adjust?
Also wondering if the code has been updated to include follow through days. Thanks in advance!
# SMO_MktVolumesDaily.ts
# Version 2.0, 2022-01-30
# 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};
# Reset distribution day counts on FTD.
input distributionRstDay = 20191010;
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
def dropThreshold;
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;
dropThreshold = .942;
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;
dropThreshold = .931;
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;
dropThreshold = .942;
}
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 irrelevant
# reset distribution count to 0
# Distribution day count should reset after 2nd confirmation day
# input distributionRstDay = 20191010; a prior 2nd confirmation day
# input distributionRstDay = 20200402; a prior 2nd confirmation day
#input distributionRstDay = 20191010;
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 reset date input by user
# It appears at the left side of the volume bar
AddVerticalLine( if (GetYYYYMMDD() == distributionRstDay ) 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 gap up 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+newStallDays;
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);
#-------------------------------------------------------------------------------------
# Follow-through signals (FTD) are more likely to fail if distribution days
# occur in the first few days of a new uptrend. This is one key red flag.
# Quantification in script is implemented with a concept of critical score (critScore):
# critScore = 3 for the 1st 5 days after FTD
# critScore = 2 on the 6th, critScore = 1 on 7th day, critScore = 0 after 7th day
# Total Distribution days = critScore + regular D-day count
# critScore is used only if there is at least one D-day in the 1st 5th day after FTD
#-------------------------------------------------------------------------------------
def ftdBar = if GetYYYYMMDD() == distributionRstDay then barnumber() else 0;
def lastFtdBar = highestall(ftdBar);
def daysAfterFTD = lastBar - lastFtdBar;
def critScore = if daysAfterFTD <= 0 then 0 else
if daysAfterFTD <= 5 then 3 else
if daysAfterFTD <= 6 then 2 else
if daysAfterFTD <= 7 then 1 else 0;
def totalNdDaysC = totalNdDays + if totalNdDays > 0 then critScore else 0;
# Actual distribution day count is shown but color depends on totalNdDaysC
addLabel(yes, "NewDdays =" + concat("",totalNdDays ),
if totalNdDaysC <=2 then Color.Green
else if totalNdDaysC <= 4 then Color.ORANGE
else Color.RED);
# Add an indication of 1st rally day to start FTD count
# in a market correction period
# pink rally day is a day satisfying the following conditions:
# 1). Close above ½ of daily TRUE range and below prior day close
# 2). Low is the lowest during the market correction,
# including future lows if available
# resolution of each 1st rally day is set to about 2 weeks
def rDayInterval = round(25/2, 0);
def futureLow = if isNaN(prLo[-rDayInterval]) then futureLow[1]
else prLo[-rDayInterval];
def futureCls = if isNaN(cls[-rDayInterval]) then futureCls[1]
else cls[-rDayInterval];
# market correction is currently defined as down about 8% from top
# need to be refined so that it will work in a bear market that is forming a bottom.
# In this case, the 8% drop may not be required.
def mktCr = prLo[1] <= highest(high, 25) * dropThreshold; #.931; #.92;
def prRng = TrueRange(prHi, cls, prLo); #prHi - prLo;
def pinkRday = cls > (prLo + prRng/2) and cls < cls[1] and
prLo <= lowest(prLo[1],rDayInterval) and
prLo <= lowest(futureLow, rDayInterval);
# The real rally day has its close higher than prior close
# A rally day is invalidated if the low is broken in subsequent days
def realRday = cls > cls[1] and
prLo <= lowest(futureLow, rDayInterval) and
sum(realRday[1], rDayInterval) == 0 and
sum(pinkRday[1], rDayInterval) == 0;
def RallyDay1 = (mktCr or mktCr[1]) and (pinkRday or realRday);
AddChartBubble(RallyDay1, vol, "R1", color.LIGHT_GREEN);
# Currently (April, 2020) a daily price (cls) increase of 1.25% minimum is
# the price requirement by either SPX or NASDAQ for a FTD.
# Must be day 4 after 1st rally attempt /w an exception (1st 3 days are strong).
# Must have higher volume on the FTD day
def lastR1Bar = if (RallyDay1, barnumber(), lastR1Bar[1]);
def daysAfterR1 = barnumber() - lastR1Bar;
def isFTD = if daysAfterR1 >= 4 and barnumber() > lastR1Bar and
barnumber() < lastR1Bar + 25 and lastR1Bar != 0 and
cls >= (cls[1] * 1.0125) then 1 else 0 ;
def oneFTD = sum(isFTD, 25) == 1 and isFTD and daysAfterR1 < 25 and
volIncrease;
AddChartBubble(oneFTD, vol, "FTD", color.LIGHT_GREEN);
@UT2Pro1689 Thank you! It's not showing an FTD for 10/14/21 like it is on yours. But I don't think it is supposed to, correct? Since volume was lower than the previous day? It's also not showing a FTD for today (1/31/22) on the NASDAQ, even though I believe it should be (day 6 of rally, up 3.4%, and volume > yesterday's volume).@rh19
I've update my script since last post here. It should help you in certain areas even though it may or may not fix the issue you mentioned. Since I have limited time for this work, I hope someone who improved the script can share the fix with you. In the meantime, I plan to keep improving when I have time.
Here's how the current Follow Through Day in my own chart looks like in the newer script for SPX. The signals on Nasdaq have minor differences at this time.
The following is the code from my TOS platform with all debug codes removed.
Code:# SMO_MktVolumesDaily.ts # Version 2.0, 2022-01-30 # 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}; # Reset distribution day counts on FTD. input distributionRstDay = 20191010; 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 def dropThreshold; 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; dropThreshold = .942; 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; dropThreshold = .931; 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; dropThreshold = .942; } 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 irrelevant # reset distribution count to 0 # Distribution day count should reset after 2nd confirmation day # input distributionRstDay = 20191010; a prior 2nd confirmation day # input distributionRstDay = 20200402; a prior 2nd confirmation day #input distributionRstDay = 20191010; 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 reset date input by user # It appears at the left side of the volume bar AddVerticalLine( if (GetYYYYMMDD() == distributionRstDay ) 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 gap up 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+newStallDays; 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); #------------------------------------------------------------------------------------- # Follow-through signals (FTD) are more likely to fail if distribution days # occur in the first few days of a new uptrend. This is one key red flag. # Quantification in script is implemented with a concept of critical score (critScore): # critScore = 3 for the 1st 5 days after FTD # critScore = 2 on the 6th, critScore = 1 on 7th day, critScore = 0 after 7th day # Total Distribution days = critScore + regular D-day count # critScore is used only if there is at least one D-day in the 1st 5th day after FTD #------------------------------------------------------------------------------------- def ftdBar = if GetYYYYMMDD() == distributionRstDay then barnumber() else 0; def lastFtdBar = highestall(ftdBar); def daysAfterFTD = lastBar - lastFtdBar; def critScore = if daysAfterFTD <= 0 then 0 else if daysAfterFTD <= 5 then 3 else if daysAfterFTD <= 6 then 2 else if daysAfterFTD <= 7 then 1 else 0; def totalNdDaysC = totalNdDays + if totalNdDays > 0 then critScore else 0; # Actual distribution day count is shown but color depends on totalNdDaysC addLabel(yes, "NewDdays =" + concat("",totalNdDays ), if totalNdDaysC <=2 then Color.Green else if totalNdDaysC <= 4 then Color.ORANGE else Color.RED); # Add an indication of 1st rally day to start FTD count # in a market correction period # pink rally day is a day satisfying the following conditions: # 1). Close above ½ of daily TRUE range and below prior day close # 2). Low is the lowest during the market correction, # including future lows if available # resolution of each 1st rally day is set to about 2 weeks def rDayInterval = round(25/2, 0); def futureLow = if isNaN(prLo[-rDayInterval]) then futureLow[1] else prLo[-rDayInterval]; def futureCls = if isNaN(cls[-rDayInterval]) then futureCls[1] else cls[-rDayInterval]; # market correction is currently defined as down about 8% from top # need to be refined so that it will work in a bear market that is forming a bottom. # In this case, the 8% drop may not be required. def mktCr = prLo[1] <= highest(high, 25) * dropThreshold; #.931; #.92; def prRng = TrueRange(prHi, cls, prLo); #prHi - prLo; def pinkRday = cls > (prLo + prRng/2) and cls < cls[1] and prLo <= lowest(prLo[1],rDayInterval) and prLo <= lowest(futureLow, rDayInterval); # The real rally day has its close higher than prior close # A rally day is invalidated if the low is broken in subsequent days def realRday = cls > cls[1] and prLo <= lowest(futureLow, rDayInterval) and sum(realRday[1], rDayInterval) == 0 and sum(pinkRday[1], rDayInterval) == 0; def RallyDay1 = (mktCr or mktCr[1]) and (pinkRday or realRday); AddChartBubble(RallyDay1, vol, "R1", color.LIGHT_GREEN); # Currently (April, 2020) a daily price (cls) increase of 1.25% minimum is # the price requirement by either SPX or NASDAQ for a FTD. # Must be day 4 after 1st rally attempt /w an exception (1st 3 days are strong). # Must have higher volume on the FTD day def lastR1Bar = if (RallyDay1, barnumber(), lastR1Bar[1]); def daysAfterR1 = barnumber() - lastR1Bar; def isFTD = if daysAfterR1 >= 4 and barnumber() > lastR1Bar and barnumber() < lastR1Bar + 25 and lastR1Bar != 0 and cls >= (cls[1] * 1.0125) then 1 else 0 ; def oneFTD = sum(isFTD, 25) == 1 and isFTD and daysAfterR1 < 25 and volIncrease; AddChartBubble(oneFTD, vol, "FTD", color.LIGHT_GREEN);
@rh19@UT2Pro1689 Thank you! It's not showing an FTD for 10/14/21 like it is on yours. But I don't think it is supposed to, correct? Since volume was lower than the previous day? It's also not showing a FTD for today (1/31/22) on the NASDAQ, even though I believe it should be (day 6 of rally, up 3.4%, and volume > yesterday's volume).
def isFTD = if daysAfterR1 >= 4 and barnumber() > lastR1Bar and
barnumber() < lastR1Bar + 25 and lastR1Bar != 0 and
cls >= (cls[1] * 1.0125) and
volIncrease then 1 else 0 ;
def oneFTD = sum(isFTD, 25) == 1 and isFTD and daysAfterR1 < 25 ;
@UT2Pro1689 Awesome, thank you!@rh19
Thanks for the feedback. I updated the following 2 lines in my script and 1/31/22 is now shown as a FTD on my chart for both SPX & Nasdaq.
Just replace original 2 lines with the above 2 new lines.Code:def isFTD = if daysAfterR1 >= 4 and barnumber() > lastR1Bar and barnumber() < lastR1Bar + 25 and lastR1Bar != 0 and cls >= (cls[1] * 1.0125) and volIncrease then 1 else 0 ; def oneFTD = sum(isFTD, 25) == 1 and isFTD and daysAfterR1 < 25 ;
I still have 10/14/21 as FTD on my SPX chart for some reason. I will investigate more about it.
@svencoolIf your using the NYSE Vol on the SPX chart. What vol do you use on the NASDAQ and NYSE? This is some great work I did a trial of MARKET SMITH and there were a few things I liked about it just didn't like the price tag per month.... Also just watching a previous Trading Summit from IBD and the chart they are showing is on a weekly time frame. So should I set my charts to daily or weekly?
Picture
@UT2Pro1689 Thank You For Getting Back! I do remember that member pointing out that they found out that the market indexes performed best during the Market in Correction condition. Like I said I've done a 4 week trial of MarketSmith some time ago. Lots of info to digest in a short period of time. But I did see some value in keeping track of these distribution days.@svencool
I think William O'Neil has described in his books about the usage of the index volumes. For SPX & Dow Jones Industrial, use NYSE vol. For NASD, use Nasd vol. I don't remember them using NYSE composite index though.
If you are trading current stock market, you should use daily chart for market directions.
A warning about using IBD system: There are usually multiple small market corrections declared by IBD before the big one comes. As the other member mentioned, these small market corrections which drop about 5% to 7% may be most profitable times for some traders to enter market at cheaper prices.
The IBD weekly chart included in your post also tells another story: market may go to the state of confirmed rally multiple times before a sustained rally comes. IBD counts distribution days only if market is in confirmed rally and there are 4 periods of distribution day counts after the top in the chart on March 2000. The chart indicated there were 4 confirmed rallies before the new bull market finally came on march 2003. These confirmed rallies all failed within a few weeks for people who want to trade longer term. The chart misled readers by hiding the indications of these 4 failed confirmed rallies in my opinion.
@svencool@UT2Pro1689 When looking at the Dow should 1/31 be considered a FTD? Attached a Pic
Dow Jones Industrial Average
Nice chartsI was wondering these distribution days. Would this be considered Institutional Selling?
The Nasdaq/Dow/SPX As Of Friday
Got it thank you!!!!Nice charts
All distribution days are considered as institutional selling by IBD.
In market correction, distribution days are not used anymore. It will be recounted after market in confirmed rally.
Currently, the script can reset distribution day count to 0 after a FTD manually.
The script has an input field named "Distribution RST Day".
You should set it to 20220131 (The latest FTD) via the config menu. Then, you will see the updated distribution day count is 2 since the FTD and a vertical virtual line on the date.
def realRday = cls > cls[1] and
(prLo <= lowest(prLo[1],rDayInterval) or
prLo[1] <= lowest(prLo[1],rDayInterval)) and
(prLo <= lowest(futureLow, rDayInterval) or
prLo[1] <= lowest(futureLow, rDayInterval)) and
sum(realRday[1], rDayInterval) == 0 and
sum(pinkRday[1], rDayInterval) == 0;
Join useThinkScript to post your question to a community of 21,000+ developers and traders.
Start a new thread and receive assistance from our community.
useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.
We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.
If you are new, or just looking for guidance, here are some helpful links to get you started.