trading-signals
trading-signals copied to clipboard
Trend Determination (Dow Theory Style)
Core definition of trends in Dow Theory:
- Uptrend = Higher Highs (HH) + Higher Lows (HL)
- Downtrend = Lower Highs (LH) + Lower Lows (LL)
Once a sequence like this is detected:
- HH → HL → HH → HL → confirms uptrend
- LL → LH → LL → LH → confirms downtrend
Dow Theory in the real world is not always clean — price action can be noisy. One or two candles might spike out of sequence, but the overall trend structure remains valid. To handle this, we can introduce a buffer or tolerance, often called:
- Deviation
- Threshold
- Leniency
- Noise buffer
Requirements:
- Maintain a rolling window of price data (
maxCandles) - Calculate pivot highs/lows based on a lookback period
- Using buffer logic to determine the trend
- Support real-time candle streaming
Zero-Lag Dow Theory Trend Detector
type PriceData = { high: number; low: number };
type Trend = 'Uptrend' | 'Downtrend' | 'Neutral';
class RealTimeDowTrend {
private data: PriceData[] = [];
private highs: number[] = [];
private lows: number[] = [];
private maxCandles: number;
private lookback: number;
private buffer: number;
constructor(maxCandles: number = 100, lookback: number = 5, buffer: number = 0.01) {
this.maxCandles = maxCandles;
this.lookback = lookback;
this.buffer = buffer;
}
private isHigher(a: number, b: number): boolean {
return a >= b * (1 - this.buffer);
}
private isLower(a: number, b: number): boolean {
return a <= b * (1 + this.buffer);
}
private detectPivotHigh(index: number): boolean {
if (index < this.lookback) return false;
const window = this.data.slice(index - this.lookback, index);
return this.data[index].high > Math.max(...window.map(d => d.high));
}
private detectPivotLow(index: number): boolean {
if (index < this.lookback) return false;
const window = this.data.slice(index - this.lookback, index);
return this.data[index].low < Math.min(...window.map(d => d.low));
}
public addCandle(candle: PriceData): Trend {
this.data.push(candle);
if (this.data.length > this.maxCandles) {
this.data.shift();
this.highs = this.highs.filter(i => i > 0).map(i => i - 1);
this.lows = this.lows.filter(i => i > 0).map(i => i - 1);
}
const idx = this.data.length - 1;
// Detect pivots using past candles only
if (this.detectPivotHigh(idx)) this.highs.push(idx);
if (this.detectPivotLow(idx)) this.lows.push(idx);
return this.determineTrend();
}
private determineTrend(): Trend {
if (this.highs.length < 2 || this.lows.length < 2) return 'Neutral';
const lastHigh = this.data[this.highs[this.highs.length - 1]].high;
const prevHigh = this.data[this.highs[this.highs.length - 2]].high;
const lastLow = this.data[this.lows[this.lows.length - 1]].low;
const prevLow = this.data[this.lows[this.lows.length - 2]].low;
const higherHigh = this.isHigher(lastHigh, prevHigh);
const higherLow = this.isHigher(lastLow, prevLow);
const lowerHigh = this.isLower(lastHigh, prevHigh);
const lowerLow = this.isLower(lastLow, prevLow);
if (higherHigh && higherLow) return 'Uptrend';
if (lowerHigh && lowerLow) return 'Downtrend';
return 'Neutral';
}
}