Charts, Trends & Dow Theory
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 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 pair alternating low/high/low/...:
(a) State the Dow-theory definition of a primary uptrend in terms of successive highs and lows. Using , 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 are , , . 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 and , derive the line equation and compute the projected support price at . Does the actual low at () 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 \40$160$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 and , assuming a scale of pixels per dollar. Show these two equal-percentage moves are visually unequal. (4)
(b) On a logarithmic (base-10) axis with scale pixels per unit, prove that both moves occupy the same vertical distance. Give that distance in terms of . (5)
(c) Show generally that on a log scale, any constant-percentage move maps to a constant vertical displacement independent of . Derive the displacement in terms of and . (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 (odd positions): . Check ✓ — strictly increasing, so HH holds. (1.5)
- Lows in (even positions): . Check ✓ — 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: : . (1)
- Leg 2: : . (1)
- Leg 3: : . (1)
- ⇒ 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 . Intercept . Line: . (1.5)
- At : . (1)
- Actual low ⇒ 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 : . (1)
- Linear pixels for : . (1)
- Ratio . (1)
- Both are 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: . (1)
- Move 1: . (1.5)
- Move 2: . (1.5)
- Equal, each pixels. (1)
(c) (5 marks)
- Displacement (2)
- . (2)
- Independent of ⇒ 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-9Marks: 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_downMarks: 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 . avg down . (1)
- and final ⇒ uptrend 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)"}
]