Trading intelligence for traders who don't have time to read the news. Built solo over a gap year. Already in the wild.
Catalyst pulls real market data from a curated mesh of providers, then scores everything through a three-tier intelligence pipeline. Each surface gets its own rhythm, its own intel.
Real-time price action, support and resistance auto-detection, dominance overlay, CME gap notices and holiday flags. Built for traders who run BTC as a primary book.
Eight major and cross pairs ingested via Frankfurter. Scoring model rebuilt from the ground up with realistic move thresholds — not hyped pip swings dressed up as signals.
Rebuilt from the NASDAQ shell using SPY as proxy through Alpha Vantage's free tier. Same architecture, asset-tuned colour, asset-tuned signal weighting.
gold-api.com powers metals pricing after evaluating three providers. Real yield context, dollar-index overlay, physical-versus-futures basis flags.
Catalyst was engineered solo using modern tooling. A React frontend deployed via Netlify, a curated API mesh across four asset classes, and a three-tier intelligence pipeline holding it all together.
Catalyst's code values clarity over cleverness. Hooks are flat, names are descriptive, every score traceable back to its tier.
// 3-tier intel pipeline:
// T1 = keyword pre-filter (cheap)
// T2 = sentiment + asset (medium)
// T3 = LLM context score (expensive)
export function useArticleIntel(articles, threshold = 60) {
const [scored, setScored] = useState([]);
useEffect(() => {
const run = async () => {
const t1 = articles.filter(keywordPass);
const t2 = await tagAndScore(t1);
const t3 = t2.filter(a => a.score >= threshold);
setScored(await llmScore(t3));
};
run();
}, [articles, threshold]);
return scored;
}
// Realistic FX move thresholds.
// 1 pip on EURUSD ≠ 1 pip on USDJPY.
const THRESHOLDS = {
EURUSD: { minor: 25, major: 60 },
GBPUSD: { minor: 35, major: 80 },
USDJPY: { minor: 20, major: 55 },
AUDUSD: { minor: 30, major: 70 }
};
export function scorePair(pair, pips) {
const t = THRESHOLDS[pair];
if (!t) return 0;
const abs = Math.abs(pips);
if (abs >= t.major) return 90;
if (abs >= t.minor) return 65;
return Math.round((abs / t.minor) * 50);
}
// Detect S/R clusters from 1H candles.
// Wicks count more than bodies.
export function findLevels(candles, tol = 0.003) {
const pivots = [];
candles.forEach((c, i) => {
if (i < 2 || i > candles.length - 3) return;
const isPeak =
c.high > candles[i-1].high &&
c.high > candles[i+1].high;
const isTrough =
c.low < candles[i-1].low &&
c.low < candles[i+1].low;
if (isPeak) pivots.push({ p: c.high, t: 'R' });
if (isTrough) pivots.push({ p: c.low, t: 'S' });
});
return cluster(pivots, tol);
}
// Threshold filter cut credits 26%.
// Skip LLM call when score < floor.
export const shouldEnrich = (article, mode) => {
const floor = {
aggressive: 45,
balanced: 60,
lean: 75
}[mode] ?? 60;
if (article.tierTwoScore < floor) return false;
if (article.ageMinutes > 45) return false;
if (article.duplicateOf) return false;
return true;
};
// Result: 122 → 90 cr/hr. Same UX.