Level 5 — MasteryCharts, Trends & Dow Theory

Charts, Trends & Dow Theory

90 minutes60 marksprintable — key stays hidden on paper

Level: 5 (Mastery — cross-domain: math + coding + theory synthesis) Time limit: 90 minutes Total marks: 60

Instructions: Answer all three questions. Show all derivations. Where code is requested, write clean, runnable Python (pseudocode not accepted for full marks). Use ...... / ...... for mathematics.


Question 1 — Trend Detection Algorithm & Higher-High/Higher-Low Logic (22 marks)

You are given daily OHLC data. A swing high is a bar whose high is strictly greater than the highs of the kk bars immediately before and after it; a swing low is defined symmetrically on lows.

Consider the following closing-swing pivot sequence extracted from a stock (chronological), each an (index,price)(index, price) pair alternating low/high/low/...:

P=[(0,100), (5,118), (9,108), (14,132), (18,121), (23,150)]P = [(0, 100),\ (5, 118),\ (9, 108),\ (14, 132),\ (18, 121),\ (23, 150)]

(a) State the Dow-theory definition of a primary uptrend in terms of successive highs and lows. Using PP, prove formally that this sequence satisfies the higher-highs / higher-lows (HH/HL) condition. (5)

(b) Write a Python function classify_trend(pivots) that takes a list of alternating swing pivots (each (index, price)) and returns one of "uptrend", "downtrend", or "range". Your logic must compare the sequence of highs against each other and the sequence of lows against each other. (8)

(c) The impulse legs (low→high moves) of PP are 100118100\to118, 108132108\to132, 121150121\to150. Compute the percentage gain of each leg and determine whether momentum (as % move per leg) is accelerating or decelerating. Comment on what a Dow theorist would want volume to do across these legs for confirmation. (6)

(d) Define a trendline connecting the swing lows. Using the two lows (0,100)(0,100) and (9,108)(9,108), derive the line equation p(t)=mt+bp(t)=mt+b and compute the projected support price at t=18t=18. Does the actual low at t=18t=18 (121121) sit above or below this line, and what does that imply? (3)


Question 2 — Log vs Linear Scale and Equal-Percentage Geometry (20 marks)

A stock rises from \40toto$160overaperiod,passingthroughover a period, passing through$80$ at the midpoint in time-of-doubling terms (i.e. two successive doublings).

(a) On a linear price axis, compute the vertical pixel distance ratio between the moves 408040\to80 and 8016080\to160, assuming a scale of cc pixels per dollar. Show these two equal-percentage moves are visually unequal. (4)

(b) On a logarithmic (base-10) axis with scale dd pixels per log10\log_{10} unit, prove that both moves occupy the same vertical distance. Give that distance in terms of dd. (5)

(c) Show generally that on a log scale, any constant-percentage move pp(1+r)p\to p(1+r) maps to a constant vertical displacement independent of pp. Derive the displacement in terms of dd and rr. (5)

(d) Write a Python function to_log_pixels(prices, d) returning pixel y-coordinates (base-10 log scaled). Then verify numerically that for prices [40, 80, 160] the two gaps are equal to within floating-point tolerance. (6)


Question 3 — Volume-Confirmed Trend & Dow Theory Synthesis (18 marks)

Dow Theory states a trend is valid until confirmation of reversal, and that volume must confirm the trend.

(a) State any three tenets of Dow Theory and explain the principle that averages (indices) must confirm each other. (6)

(b) You have two arrays: close (prices) and vol (volume). Design and code a function volume_confirms(close, vol) that returns True if, over an uptrend (final close > first close), the average volume on up-days exceeds the average volume on down-days. Define an up-day as a day whose close exceeds the previous close. (8)

(c) Given the data below, apply your criterion by hand and state whether the uptrend is volume-confirmed. (4)

Day 1 2 3 4 5
Close 100 104 102 108 110
Vol 500 300 700 600
Answer keyMark scheme & solutions

Question 1

(a) (5 marks)

  • Dow uptrend definition: a series of successively higher highs (HH) and higher lows (HL) — each rally peak exceeds the prior peak and each reaction trough is above the prior trough. (2)
  • Highs in PP (odd positions): 118,132,150118, 132, 150. Check 118<132<150118 < 132 < 150 ✓ — strictly increasing, so HH holds. (1.5)
  • Lows in PP (even positions): 100,108,121100, 108, 121. Check 100<108<121100 < 108 < 121 ✓ — strictly increasing, so HL holds. (1.5)
  • Conclusion: both HH and HL satisfied ⇒ confirmed uptrend.

(b) (8 marks)

def classify_trend(pivots):
    prices = [p for _, p in pivots]
    highs = prices[1::2]   # positions 1,3,5,... are highs
    lows  = prices[0::2]   # positions 0,2,4,... are lows
 
    def strictly_increasing(xs):
        return all(a < b for a, b in zip(xs, xs[1:]))
    def strictly_decreasing(xs):
        return all(a > b for a, b in zip(xs, xs[1:]))
 
    hh = strictly_increasing(highs)
    hl = strictly_increasing(lows)
    lh = strictly_decreasing(highs)
    ll = strictly_decreasing(lows)
 
    if hh and hl:
        return "uptrend"
    if lh and ll:
        return "downtrend"
    return "range"

Marks: correct high/low separation (2), increasing/decreasing helpers (2), uptrend logic (2), downtrend logic (1), range fallback (1).

(c) (6 marks)

  • Leg 1: 100118100\to118: 18100=18.0%\frac{18}{100}=18.0\%. (1)
  • Leg 2: 108132108\to132: 24108=22.22%\frac{24}{108}=22.22\%. (1)
  • Leg 3: 121150121\to150: 29121=23.97%\frac{29}{121}=23.97\%. (1)
  • 18.0%<22.22%<23.97%18.0\% < 22.22\% < 23.97\% ⇒ momentum (% gain per leg) is accelerating. (1)
  • Dow confirmation: a healthy accelerating/continuing uptrend should show volume expanding on the up-legs (rallies) and contracting on pullbacks; rising volume confirms the primary uptrend. (2)

(d) (3 marks)

  • Slope m=10810090=890.8889m = \frac{108-100}{9-0} = \frac{8}{9} \approx 0.8889. Intercept b=100b=100. Line: p(t)=89t+100p(t)=\frac{8}{9}t + 100. (1.5)
  • At t=18t=18: p(18)=89(18)+100=16+100=116p(18)=\frac{8}{9}(18)+100 = 16+100 = 116. (1)
  • Actual low 121>116121 > 116 ⇒ price sits above the trendline; the low is holding above projected support — bullish, uptrend intact (lows rising faster than the base trendline). (0.5)

Question 2

(a) (4 marks)

  • Linear pixels for 408040\to80: (8040)c=40c(80-40)\cdot c = 40c. (1)
  • Linear pixels for 8016080\to160: (16080)c=80c(160-80)\cdot c = 80c. (1)
  • Ratio =80c40c=2= \frac{80c}{40c}=2. (1)
  • Both are +100%+100\% moves yet the second occupies twice the vertical space — equal-percentage moves are visually unequal on a linear scale. (1)

(b) (5 marks)

  • Pixel positions: y=dlog10py=d\log_{10}p. (1)
  • Move 1: d(log1080log1040)=dlog102d(\log_{10}80-\log_{10}40)=d\log_{10}2. (1.5)
  • Move 2: d(log10160log1080)=dlog102d(\log_{10}160-\log_{10}80)=d\log_{10}2. (1.5)
  • Equal, each =dlog1020.30103d=d\log_{10}2 \approx 0.30103\,d pixels. (1)

(c) (5 marks)

  • Displacement =d(log10(p(1+r))log10p)=d(\log_{10}(p(1+r))-\log_{10}p) (2)
  • =dlog10 ⁣p(1+r)p=dlog10(1+r)=d\log_{10}\!\frac{p(1+r)}{p}=d\log_{10}(1+r). (2)
  • Independent of pp ⇒ constant-percentage moves have constant vertical size on a log axis. (1)

(d) (6 marks)

import math
def to_log_pixels(prices, d):
    return [d * math.log10(p) for p in prices]
 
y = to_log_pixels([40, 80, 160], d=100)
gap1 = y[1] - y[0]
gap2 = y[2] - y[1]
assert abs(gap1 - gap2) < 1e-9

Marks: function correct (3), gap computation (2), tolerance check (1).


Question 3

(a) (6 marks) Any three of the tenets (2 marks each, cap 6, but part reserves confirmation point):

  • The averages discount everything.
  • The market has three trends (primary, secondary, minor).
  • Primary trends have three phases (accumulation, public participation, distribution).
  • Volume must confirm the trend.
  • A trend is assumed in effect until it gives definite reversal signals.
  • Averages must confirm each other: a new trend signal (e.g. new high in the Industrials) is only valid if a second index (e.g. Transports) confirms with a similar move; divergence casts doubt on the signal. (2 of the 6 for this explanation)

(b) (8 marks)

def volume_confirms(close, vol):
    if close[-1] <= close[0]:
        return False  # not an uptrend
    up_vols, down_vols = [], []
    for i in range(1, len(close)):
        if vol[i] is None:
            continue
        if close[i] > close[i-1]:
            up_vols.append(vol[i])
        elif close[i] < close[i-1]:
            down_vols.append(vol[i])
    if not up_vols:
        return False
    avg_up = sum(up_vols) / len(up_vols)
    avg_down = sum(down_vols) / len(down_vols) if down_vols else 0
    return avg_up > avg_down

Marks: uptrend guard (2), up/down classification vs previous close (3), averaging (2), comparison return (1).

(c) (4 marks)

  • Day2 up (104>100): vol 500 → up. Day3 down (102<104): 300 → down. Day4 up (108>102): 700 → up. Day5 up (110>108): 600 → up. (2)
  • avg up =500+700+6003=600=\frac{500+700+600}{3}=600. avg down =300=300. (1)
  • 600>300600 > 300 and final 110>100110>100uptrend is volume-confirmed (True). (1)
[
  {"claim":"Leg percentage gains are 18, 22.22..., 23.97... and increasing", "code":"g1=Rational(18,100)*100; g2=Rational(24,108)*100; g3=Rational(29,121)*100; result = bool(g1<g2<g3 and g1==18)"},
  {"claim":"Trendline through lows gives support 116 at t=18", "code":"m=Rational(8,9); b=100; p=m*18+b; result = (p==116)"},
  {"claim":"Log-scale gaps 40->80 and 80->160 are equal (both log10 2)", "code":"g1=log(80,10)-log(40,10); g2=log(160,10)-log(80,10); result = bool(simplify(g1-g2)==0)"},
  {"claim":"Volume confirmation: avg up 600 > avg down 300", "code":"up=(500+700+600)/S(3); down=S(300); result = bool(up==600 and up>down)"}
]