Loading...
Complete signal engine transparency — no black boxes
Every signal TradeClaw generates is produced by deterministic, open-source TypeScript code. This page documents exactly how it works — the data sources, indicator math, scoring formula, quality gates, and TP/SL calculation. If you disagree with any of it,submit a PR.
This is the actual function from apps/web/app/lib/signal-generator.ts. Simplified for clarity — see GitHub for full implementation.
// Signal confidence scoring — apps/web/app/lib/signal-generator.ts
function scoreSignal(indicators: AllIndicators, direction: 'BUY' | 'SELL'): number {
let score = 0;
const { rsi, macd, ema, bollinger, stochastic } = indicators;
const isBuy = direction === 'BUY';
// ── RSI component (max 20 pts) ──────────────────────────
if (isBuy) {
if (rsi.current < 30) score += 20; // Oversold: strong BUY signal
else if (rsi.current < 40) score += 15; // Approaching oversold
else if (rsi.current < 50) score += 8; // Below midline
else if (rsi.current > 70) score -= 10; // Overbought: penalise BUY
} else {
if (rsi.current > 70) score += 20; // Overbought: strong SELL signal
else if (rsi.current > 60) score += 15;
else if (rsi.current > 50) score += 8;
else if (rsi.current < 30) score -= 10;
}
// ── MACD component (max 20 pts) ─────────────────────────
const { histogram, current: macdCurrent } = macd;
const histLen = histogram.length;
if (histLen >= 2) {
const histChanging = isBuy
? histogram[histLen-1] > histogram[histLen-2] // histogram increasing
: histogram[histLen-1] < histogram[histLen-2];
if (histChanging) score += 10;
}
if (isBuy && macdCurrent.macd > macdCurrent.signal) score += 10;
if (!isBuy && macdCurrent.macd < macdCurrent.signal) score += 10;
// ── EMA trend alignment (max 20 pts) ────────────────────
const { current: emaCurrent } = ema;
const price = indicators.closes[indicators.closes.length - 1];
if (isBuy) {
if (price > emaCurrent.ema20) score += 7;
if (price > emaCurrent.ema50) score += 7;
if (emaCurrent.ema20 > emaCurrent.ema50) score += 6; // golden cross
} else {
if (price < emaCurrent.ema20) score += 7;
if (price < emaCurrent.ema50) score += 7;
if (emaCurrent.ema20 < emaCurrent.ema50) score += 6; // death cross
}
// ── Bollinger Bands (max 15 pts) ────────────────────────
const { current: bb } = bollinger;
if (isBuy && price < bb.lower) score += 15; // Price at lower band
else if (isBuy && price < bb.middle) score += 8;
if (!isBuy && price > bb.upper) score += 15; // Price at upper band
else if (!isBuy && price > bb.middle) score += 8;
// ── Stochastic (max 15 pts) ──────────────────────────────
const { current: stoch } = stochastic;
if (isBuy && stoch.k < 20) score += 15; // Stochastic oversold
else if (isBuy && stoch.k < 40) score += 8;
if (!isBuy && stoch.k > 80) score += 15; // Stochastic overbought
else if (!isBuy && stoch.k > 60) score += 8;
// ── Normalise to 0–100 ──────────────────────────────────
const maxScore = 90;
const rawPct = Math.max(0, Math.min(100, (score / maxScore) * 100));
// Map to 50-98% range (we never claim 100% certainty)
return 50 + rawPct * 0.48;
}Wilder's original RSI formula, implemented in TypeScript. File: apps/web/app/lib/ta-engine.ts
// RSI calculation — apps/web/app/lib/ta-engine.ts
function calculateRSI(closes: number[], period = 14): RSIResult {
const result: number[] = new Array(closes.length).fill(NaN);
if (closes.length < period + 1) return { values: result, current: NaN };
let gains = 0, losses = 0;
// First RSI: simple average of first period
for (let i = 1; i <= period; i++) {
const delta = closes[i] - closes[i - 1];
if (delta > 0) gains += delta;
else losses -= delta;
}
let avgGain = gains / period;
let avgLoss = losses / period;
result[period] = avgLoss === 0 ? 100 : 100 - (100 / (1 + avgGain / avgLoss));
// Wilder's smoothing for subsequent values
for (let i = period + 1; i < closes.length; i++) {
const delta = closes[i] - closes[i - 1];
const gain = delta > 0 ? delta : 0;
const loss = delta < 0 ? -delta : 0;
avgGain = (avgGain * (period - 1) + gain) / period;
avgLoss = (avgLoss * (period - 1) + loss) / period;
result[i] = avgLoss === 0 ? 100 : 100 - (100 / (1 + avgGain / avgLoss));
}
return { values: result, current: result[result.length - 1] };
}Levels derived from swing highs/lows in the last 20 candles, with ATR fallback.
// Take-profit / stop-loss calculation — signal-generator.ts
function calculateTPSL(
direction: 'BUY' | 'SELL',
entry: number,
highs: number[],
lows: number[],
atr: number,
) {
const recentHighs = highs.slice(-20).sort((a, b) => b - a);
const recentLows = lows.slice(-20).sort((a, b) => a - b);
const resistance = recentHighs[2]; // 3rd highest high = resistance
const support = recentLows[2]; // 3rd lowest low = support
if (direction === 'BUY') {
// TP: nearest resistance above entry, or ATR-based fallback
const tp1 = resistance > entry
? resistance
: entry + atr * 1.5;
// SL: nearest support below entry, or ATR-based fallback
const sl = support < entry
? support
: entry - atr * 1.0;
return { tp1, sl, rr: (tp1 - entry) / (entry - sl) };
} else {
const tp1 = support < entry
? support
: entry - atr * 1.5;
const sl = resistance > entry
? resistance
: entry + atr * 1.0;
return { tp1, sl, rr: (entry - tp1) / (sl - entry) };
}
}Crypto pairs (BTC, ETH, etc.) — 200 hourly candles via /api/v3/klines. No API key required for public data.
Forex (EUR/USD, GBP/USD, USD/JPY) and commodities (XAU, XAG) — scraped from Yahoo chart API.
If both APIs fail: deterministic OHLCV generation using seeded Gaussian noise. Labeled [DEMO] in UI.
TradeClaw signals are generated by algorithmic rules, not human analysis. Past signal accuracy does not guarantee future results. This tool is for educational and research purposes only. Always do your own research before trading.See accuracy page and calibration page for actual historical performance.
Found a bug in the algorithm? Want to improve the scoring?
View source on GitHub