Triple EMA trend-following strategy for NVIDIA. Enters when fast, medium, and slow EMAs align bullish.
Uses three Exponential Moving Averages (8, 21, 55 periods) to confirm trend direction. Only enters when all three align: fast > medium > slow = bullish trend. This triple confirmation reduces false signals compared to simple dual-MA crossovers. NVDA's strong trending behavior makes it an excellent candidate for this approach.
5Downloads
8Views
1Symbols
1Backtests
Parameters
fast8
medium21
slow55
positionPct90
Symbols
Backtest Results
| Symbol | Period | Return | Sharpe | Max DD | Win Rate | Trades |
|---|---|---|---|---|---|---|
| NVDA | 2025-02-24 — 2026-02-23 | 20.67% | 0.9 | -24.25% | 66.67% | 3 |
Source Code
#!/usr/bin/env node
// NVDA Triple EMA Trend — JC Trading Bots
// https://trading.jc.holdings/bot/nvda-triple-ema-trend-xy9p
// License: MIT | Free forever | Modify however you want
//
// DISCLAIMER: Not financial advice. Past performance does not guarantee
// future results. Trading involves substantial risk of loss.
// Use at your own risk. No guarantees of profit.
//
// Usage:
// node nvda-triple-ema-trend-xy9p.js # Print signals
// ALPACA_KEY=x ALPACA_SECRET=y node nvda-triple-ema-trend-xy9p.js # Paper trade
// ALPACA_KEY=x ALPACA_SECRET=y LIVE=1 node nvda-triple-ema-trend-xy9p.js # REAL money
// ─── Configuration ─────────────────────────────────────────
const CONFIG = {
"fast": 8,
"medium": 21,
"slow": 55,
"positionPct": 90
};
const SYMBOLS = ["NVDA"];
// ─── Technical Indicators ───────────────────────────────────
function sma(data, period) {
if (data.length < period) return null;
const slice = data.slice(-period);
return slice.reduce((a, b) => a + b, 0) / period;
}
function ema(data, period) {
if (data.length < period) return null;
const k = 2 / (period + 1);
let val = sma(data.slice(0, period), period);
for (let i = period; i < data.length; i++) {
val = data[i] * k + val * (1 - k);
}
return val;
}
function rsi(closes, period = 14) {
if (closes.length < period + 1) return 50;
let gains = 0, losses = 0;
for (let i = closes.length - period; i < closes.length; i++) {
const diff = closes[i] - closes[i - 1];
if (diff > 0) gains += diff;
else losses -= diff;
}
if (losses === 0) return 100;
const rs = (gains / period) / (losses / period);
return 100 - (100 / (1 + rs));
}
function bollingerBands(closes, period = 20, mult = 2) {
const mid = sma(closes.slice(-period), period);
if (!mid) return { upper: null, mid: null, lower: null };
const slice = closes.slice(-period);
const variance = slice.reduce((sum, v) => sum + Math.pow(v - mid, 2), 0) / period;
const std = Math.sqrt(variance);
return { upper: mid + mult * std, mid, lower: mid - mult * std };
}
function atr(candles, period = 14) {
if (candles.length < period + 1) return 0;
let sum = 0;
for (let i = candles.length - period; i < candles.length; i++) {
const tr = Math.max(
candles[i].high - candles[i].low,
Math.abs(candles[i].high - candles[i - 1].close),
Math.abs(candles[i].low - candles[i - 1].close)
);
sum += tr;
}
return sum / period;
}
function macd(closes, fast = 12, slow = 26, sig = 9) {
if (closes.length < slow + sig) return { macd: 0, signal: 0, histogram: 0 };
const macdSeries = [];
for (let i = slow; i <= closes.length; i++) {
const slice = closes.slice(0, i);
const f = ema(slice, fast);
const s = ema(slice, slow);
if (f !== null && s !== null) macdSeries.push(f - s);
}
if (macdSeries.length < sig) return { macd: 0, signal: 0, histogram: 0 };
const macdLine = macdSeries[macdSeries.length - 1];
const signalLine = ema(macdSeries, sig);
return { macd: macdLine, signal: signalLine || 0, histogram: macdLine - (signalLine || 0) };
}
// ─── Data Fetching (Yahoo Finance) ──────────────────────────
async function fetchCandles(symbol, range = "1y", interval = "1d") {
const url = "https://query1.finance.yahoo.com/v8/finance/chart/" + symbol
+ "?range=" + range + "&interval=" + interval + "&includePrePost=false";
const res = await fetch(url, {
headers: { "User-Agent": "Mozilla/5.0 JCTradingBot/1.0" }
});
const json = await res.json();
const result = json.chart.result[0];
const ts = result.timestamp;
const q = result.indicators.quote[0];
const candles = [];
for (let i = 0; i < ts.length; i++) {
if (q.close[i] == null) continue;
candles.push({
timestamp: ts[i] * 1000,
date: new Date(ts[i] * 1000).toISOString().split("T")[0],
open: q.open[i],
high: q.high[i],
low: q.low[i],
close: q.close[i],
volume: q.volume[i] || 0,
});
}
return candles;
}
// ─── Alpaca Integration (optional) ──────────────────────────
// Set ALPACA_KEY and ALPACA_SECRET env vars to enable trading.
// Add LIVE=1 for real money (default is paper trading).
async function alpacaTrade(symbol, action, reason) {
const key = process.env.ALPACA_KEY;
const secret = process.env.ALPACA_SECRET;
if (!key || !secret) {
console.log("[DRY RUN] " + action.toUpperCase() + " " + symbol + " — " + reason);
return;
}
const baseUrl = process.env.LIVE === "1"
? "https://api.alpaca.markets"
: "https://paper-api.alpaca.markets";
// Get account buying power
const acct = await fetch(baseUrl + "/v2/account", {
headers: { "APCA-API-KEY-ID": key, "APCA-API-SECRET-KEY": secret }
}).then(r => r.json());
if (action === "buy") {
const budget = parseFloat(acct.buying_power) * 0.9;
if (budget < 10) { console.log("Insufficient funds: $" + acct.buying_power); return; }
const price = await fetch(baseUrl + "/v2/stocks/" + symbol + "/quotes/latest", {
headers: { "APCA-API-KEY-ID": key, "APCA-API-SECRET-KEY": secret }
}).then(r => r.json());
const qty = Math.floor(budget / (price.quote?.ap || 999999));
if (qty < 1) { console.log("Price too high for budget"); return; }
const order = await fetch(baseUrl + "/v2/orders", {
method: "POST",
headers: { "APCA-API-KEY-ID": key, "APCA-API-SECRET-KEY": secret, "Content-Type": "application/json" },
body: JSON.stringify({ symbol, qty: String(qty), side: "buy", type: "market", time_in_force: "day" })
}).then(r => r.json());
console.log("ORDER PLACED: BUY " + qty + " " + symbol + " — " + reason);
console.log("Order ID: " + order.id);
} else if (action === "sell") {
// Close position
const close = await fetch(baseUrl + "/v2/positions/" + symbol, {
method: "DELETE",
headers: { "APCA-API-KEY-ID": key, "APCA-API-SECRET-KEY": secret }
}).then(r => r.json());
console.log("POSITION CLOSED: " + symbol + " — " + reason);
}
}
const BOT_NAME = "NVDA Triple EMA Trend Follower";
const DATA_RANGE = "1y";
function evaluate(candles, config) {
const closes = candles.map(c => c.close);
const fast = ema(closes, config.fastPeriod || 8);
const mid = ema(closes, config.midPeriod || 21);
const slow = ema(closes, config.slowPeriod || 55);
const price = closes[closes.length - 1];
if (!fast || !mid || !slow) return { action: "hold", reason: "Not enough data" };
const bullish = fast > mid && mid > slow;
const bearish = fast < mid && mid < slow;
// Check alignment just changed
const prevCloses = closes.slice(0, -1);
const pf = ema(prevCloses, config.fastPeriod || 8);
const pm = ema(prevCloses, config.midPeriod || 21);
const ps = ema(prevCloses, config.slowPeriod || 55);
const wasBullish = pf > pm && pm > ps;
const wasBearish = pf < pm && pm < ps;
if (bullish && !wasBullish) {
return { action: "buy", reason: "Triple EMA aligned BULLISH. Fast " + fast.toFixed(2) + " > Mid " + mid.toFixed(2) + " > Slow " + slow.toFixed(2) };
}
if (bearish && !wasBearish) {
return { action: "sell", reason: "Triple EMA aligned BEARISH. Fast " + fast.toFixed(2) + " < Mid " + mid.toFixed(2) + " < Slow " + slow.toFixed(2) };
}
return { action: "hold", reason: "EMA Fast:" + fast.toFixed(2) + " Mid:" + mid.toFixed(2) + " Slow:" + slow.toFixed(2) + " | " + (bullish ? "BULLISH" : bearish ? "BEARISH" : "MIXED") };
}
// ─── Main ───────────────────────────────────────────────────
async function run() {
console.log("\n" + "=".repeat(60));
console.log(" " + BOT_NAME);
console.log(" " + new Date().toISOString());
console.log("=".repeat(60) + "\n");
for (const symbol of SYMBOLS) {
try {
console.log("Fetching data for " + symbol + "...");
const candles = await fetchCandles(symbol, DATA_RANGE);
console.log("Got " + candles.length + " candles (" + candles[0].date + " to " + candles[candles.length-1].date + ")");
const signal = evaluate(candles, CONFIG);
const icon = signal.action === "buy" ? "BUY" : signal.action === "sell" ? "SELL" : "HOLD";
console.log("\n " + symbol + ": " + icon + " — " + signal.reason);
if (signal.action !== "hold") {
await alpacaTrade(symbol, signal.action, signal.reason);
}
console.log("");
} catch (err) {
console.error("Error on " + symbol + ": " + err.message);
}
}
}
run().catch(err => { console.error(err); process.exit(1); });
Download .js File
Full source code included. MIT License. Free forever.