Trading Strategystockscrypto
Time-Series Momentum (12-1) Strategy
Academic momentum strategy. Uses 12-month lookback excluding the most recent month (12-1 factor model).
What is Time-Series Momentum (12-1)?
Academic momentum strategy based on long-term price continuation. Uses 12-month lookback excluding the most recent month (12-1 factor model). Best for stocks and crypto with sufficient history. Enters when momentum exceeds threshold; exits when momentum flips. Default 252 bars lookback, 21 skip.
Strategy Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| lookback | number | 252 | Lookback period (e.g. 12 months) |
| skip | number | 21 | Skip recent bars (e.g. 1 month) |
Use Cases
- ✓Long-term momentum (weekly/monthly)
- ✓Academic 12-1 factor
- ✓Stocks and crypto with history
- ✓Position trading
Strategy Script (JavaScript)
This strategy runs in VaultCharts using the built-in strategy engine. Below is the full script in a readable format. You can copy it or run it directly in VaultCharts.
strategy.jsVaultCharts built-in
module.exports = {
meta: {
name: "Time-Series Momentum",
params: {
lookback: { type: "number", default: 252 },
skip: { type: "number", default: 21 }
}
},
compute: (data, params, utils) => {
// Data sanitization
const cleanData = data.filter(d =>
d &&
Number.isFinite(d.close) &&
Number.isFinite(d.time) &&
d.close > 0
);
if (!cleanData || cleanData.length === 0) {
console.warn('[Time-Series Momentum] No valid data');
return { signals: [] };
}
const lookback = params?.lookback ?? 252;
const skip = params?.skip ?? 21;
const requiredLength = lookback + skip;
if (cleanData.length < requiredLength) {
console.warn('[Time-Series Momentum] Insufficient data:', cleanData.length, 'required:', requiredLength);
return { signals: [] };
}
const signals = [];
let checkedCount = 0;
let positiveCount = 0;
// Start from requiredLength to ensure we have valid indices
for (let i = requiredLength; i < cleanData.length; i++) {
const candle = cleanData[i];
if (!candle || candle.time === undefined) continue;
const pastIdx = i - lookback - skip;
const recentIdx = i - skip;
// Bounds check
if (pastIdx < 0 || recentIdx < 0 || pastIdx >= cleanData.length || recentIdx >= cleanData.length) continue;
const past = cleanData[pastIdx];
const recent = cleanData[recentIdx];
if (!past || past.close === undefined || !recent || recent.close === undefined) continue;
if (!Number.isFinite(past.close) || !Number.isFinite(recent.close)) continue;
checkedCount++;
const momentumReturn = (recent.close - past.close) / past.close;
const momentumPositive = momentumReturn > 0;
const momentumNegative = momentumReturn < 0;
const momentumThreshold = 0.05; // 5%
const strongMomentumUp = momentumReturn > momentumThreshold;
const strongMomentumDown = momentumReturn < -momentumThreshold;
if (momentumPositive) positiveCount++;
// Get last signal to determine position state
const lastSignal = signals.length > 0 ? signals[signals.length - 1] : null;
const wasLong = lastSignal && lastSignal.type === 'entry' && lastSignal.direction === 'long';
const wasShort = lastSignal && lastSignal.type === 'entry' && lastSignal.direction === 'short';
// Entry signals
if (strongMomentumUp && !wasLong && !wasShort) {
signals.push({ type: "entry", direction: "long", time: candle.time, price: candle.close, index: i });
} else if (strongMomentumDown && !wasLong && !wasShort) {
signals.push({ type: "entry", direction: "short", time: candle.time, price: candle.close, index: i });
}
// Exit signals
else if (momentumNegative && wasLong) {
signals.push({ type: "exit", direction: "long", time: candle.time, price: candle.close, index: i });
} else if (momentumPositive && wasShort) {
signals.push({ type: "exit", direction: "short", time: candle.time, price: candle.close, index: i });
}
}
console.log('[Time-Series Momentum] Analysis:', {
dataLength: cleanData.length,
requiredLength,
checkedCount,
positiveCount,
signalsGenerated: signals.length
});
return { signals };
}
};Run Time-Series Momentum (12-1) in VaultCharts
VaultCharts includes this strategy as a built-in option. Backtest it, adjust parameters, and use it on your own data—all stored locally on your device.