PPO (Percent Price Oscillator)
The PPO measures momentum as the percentage difference between two moving averages. In Quanthop, PPO uses Laguerre filters for smoother, less noisy signals.
Declaration
function init(ctx) { // Standard PPO ctx.indicator('ppo', 'PPO', { fastPeriod: 12, slowPeriod: 26, signalPeriod: 9 }); // Laguerre-based PPO (smoother, used in TDR) ctx.indicator('ppoRisk', 'PPO', { shortGamma: 0.4, longGamma: 0.8, accuracy: 10, source: 'close' }); }
Options
Standard Mode
| Option | Type | Default | Description |
|---|---|---|---|
fastPeriod | number | 12 | Fast EMA period |
slowPeriod | number | 26 | Slow EMA period |
signalPeriod | number | 9 | Signal line period |
source | string | 'close' | Price source |
Laguerre Mode
| Option | Type | Default | Description |
|---|---|---|---|
shortGamma | number | 0.4 | Fast Laguerre filter gamma (0-1) |
longGamma | number | 0.8 | Slow Laguerre filter gamma (0-1) |
accuracy | number | 10 | Calculation precision (higher = more bars) |
source | string | 'close' | Price source |
Output
Returns a single series of values (typically -100 to +100 range):
function onBar(ctx, i) { const ppoValue = ctx.ind.ppo[i]; // Value interpretation: // Positive: Price above its average (bullish momentum) // Negative: Price below its average (bearish momentum) // Extreme values: Overbought/oversold conditions }
Use Cases
Risk Zone Detection
Define risk zones for position management:
function define(ctx) { ctx.param('lowRiskThreshold', { type: 'int', default: -85, min: -100, max: -50 }); ctx.param('highRiskThreshold', { type: 'int', default: 85, min: 50, max: 100 }); } function onBar(ctx, i) { const ppo = ctx.ind.ppo[i]; if (ppo < ctx.p.lowRiskThreshold) { // LOW RISK ZONE: Good for entries // Market is oversold, potential reversal } else if (ppo > ctx.p.highRiskThreshold) { // HIGH RISK ZONE: Consider taking profits // Market is overbought, extended move } else { // NEUTRAL ZONE: Normal market conditions } }
Zone Duration Trading
Track how long the market stays in a risk zone:
function init(ctx) { ctx.indicator('ppo', 'PPO', { shortGamma: 0.4, longGamma: 0.8, accuracy: 10 }); ctx.state.lowRiskBars = 0; ctx.state.avgLowRiskLength = 0; ctx.state.lowRiskZoneCount = 0; } function onBar(ctx, i) { const ppo = ctx.ind.ppo[i]; const inLowRisk = ppo < -85; if (inLowRisk) { ctx.state.lowRiskBars++; // Entry signal: in zone for average duration if (ctx.state.lowRiskBars === Math.round(ctx.state.avgLowRiskLength)) { ctx.order.market('ASSET', 0.1, { signal: 'lowRiskEntry' }); } } else if (ctx.state.lowRiskBars > 0) { // Zone ended - update average ctx.state.lowRiskZoneCount++; ctx.state.avgLowRiskLength = ( ctx.state.avgLowRiskLength * (ctx.state.lowRiskZoneCount - 1) + ctx.state.lowRiskBars ) / ctx.state.lowRiskZoneCount; ctx.state.lowRiskBars = 0; } }
Momentum Confirmation
Use PPO to confirm trend signals:
function onBar(ctx, i) { const ema = ctx.ind.ema; const baseline = ctx.ind.baseline_middle; const ppo = ctx.ind.ppo[i]; // Only enter long if PPO not in high-risk zone if (q.crossOver(ema, baseline, i) && ppo < 70) { ctx.order.market('ASSET', 1, { signal: 'buy' }); } }
Value Ranges
| Range | Interpretation |
|---|---|
| < -85 | Strongly oversold (low risk for longs) |
| -85 to -50 | Moderately oversold |
| -50 to 0 | Weak bearish momentum |
| 0 to 50 | Weak bullish momentum |
| 50 to 85 | Moderately overbought |
| > 85 | Strongly overbought (high risk for longs) |
Related
- Donchian Indicator - Often paired with PPO for trend + risk
- TDR Strategy - Complete example using PPO risk zones
- init() - Indicator declaration
indicatorppomomentumoscillatorrisklaguerreqsl