DocsExamples
Multi-Indicator Strategy
A more robust strategy combining trend, momentum, and volatility filters.
Code
/** * Multi-Indicator Strategy * * Combines: * - EMA for trend direction * - RSI for momentum filter * - ATR for volatility-based stops */ function define(ctx) { // Trend ctx.param('emaLength', { type: 'int', label: 'EMA Length', default: 50, min: 20, max: 200, group: 'Trend' }); // Momentum ctx.param('rsiPeriod', { type: 'int', label: 'RSI Period', default: 14, min: 2, max: 30, group: 'Momentum' }); ctx.param('rsiLow', { type: 'int', label: 'RSI Buy Zone', default: 40, min: 20, max: 50, group: 'Momentum' }); // Risk ctx.param('atrPeriod', { type: 'int', label: 'ATR Period', default: 14, min: 5, max: 30, group: 'Risk' }); ctx.param('atrMultiplier', { type: 'float', label: 'ATR Stop Multiplier', default: 2.0, min: 1.0, max: 4.0, step: 0.5, group: 'Risk' }); } function init(ctx) { ctx.indicator('ema', 'EMA', { period: ctx.p.emaLength, source: 'close' }); ctx.indicator('rsi', 'RSI', { period: ctx.p.rsiPeriod }); ctx.indicator('atr', 'ATR', { period: ctx.p.atrPeriod }); ctx.state.entryPrice = null; ctx.state.stopPrice = null; } function onBar(ctx, i) { const ema = ctx.ind.ema[i]; const rsi = ctx.ind.rsi[i]; const atr = ctx.ind.atr[i]; const close = ctx.series.close[i]; // Wait for all indicators if (q.isNaN(ema) || q.isNaN(rsi) || q.isNaN(atr)) return; const pos = ctx.position('ASSET'); // ═══════════════════════════════════════════ // ENTRY LOGIC // ═══════════════════════════════════════════ if (pos.qty === 0) { // Trend filter: price above EMA const trendUp = close > ema; // Momentum filter: RSI not overbought const momentumOk = rsi < ctx.p.rsiLow; // EMA rising const emaTrending = q.rising(ctx.ind.ema, i); if (trendUp && momentumOk && emaTrending) { ctx.order.market('ASSET', 1, { signal: 'buy', reason: 'trend_momentum_aligned' }); ctx.state.entryPrice = close; ctx.state.stopPrice = close - (atr * ctx.p.atrMultiplier); } } // ═══════════════════════════════════════════ // EXIT LOGIC // ═══════════════════════════════════════════ if (pos.qty > 0) { // Update trailing stop (only moves up) const newStop = close - (atr * ctx.p.atrMultiplier); if (newStop > ctx.state.stopPrice) { ctx.state.stopPrice = newStop; } // Check stop if (close < ctx.state.stopPrice) { ctx.order.close('ASSET', { signal: 'stop_loss', reason: 'atr_trailing_stop' }); return; } // Trend reversal exit if (close < ema) { ctx.order.close('ASSET', { signal: 'sell', reason: 'trend_reversal' }); } } }
Key Concepts
Multiple Filter Approach
Combine filters for higher-quality signals:
const trendUp = close > ema; // Trend filter const momentumOk = rsi < 40; // Not overbought const emaTrending = q.rising(ema, i); // Trend strength if (trendUp && momentumOk && emaTrending) { // All conditions aligned }
ATR-Based Stops
Use ATR for volatility-adjusted stops:
// Stop = current price - (ATR × multiplier) ctx.state.stopPrice = close - (atr * ctx.p.atrMultiplier);
Trailing Stop Logic
Only move stop in favorable direction:
const newStop = close - (atr * multiplier); if (newStop > ctx.state.stopPrice) { ctx.state.stopPrice = newStop; // Only ratchet up }
Variations
Add Volume Filter
function init(ctx) { // ... existing indicators ctx.state.avgVolume = null; } function onBar(ctx, i) { const volume = ctx.series.volume[i]; const avgVolume = q.avg(ctx.series.volume, 20, i); // Only trade on above-average volume const volumeOk = volume > avgVolume * 1.2; if (trendUp && momentumOk && volumeOk) { // Entry } }
Add Time Filter
function onBar(ctx, i) { // Don't enter in last 10 bars const totalBars = ctx.series.close.length; if (i > totalBars - 10) return; // ... rest of logic }
Related
- Helper Functions — q.rising(), q.avg()
- ctx.state — Managing state
Related
exampleadvancedemarsiatrfilterqsl