Automated Trading with Claude Code — Part 5/6

Discord Pipeline

Zero-touch automation — the complete daily pipeline that scans markets, scores setups, and delivers A++ recommendations to your Discord every single day.

Discord Bot Scheduled Tasks Rich Embeds Zero-Touch
Automated Trading with Claude Code 5/6
The Dream Bot Architecture Rich Embeds Scheduling Daily Flow A++ Selection Monitoring

The Dream: Zero-Touch Trading

Here's what your day looks like once the pipeline is running:

07:00 AM
Morning Briefing lands in #market-briefing. Regime status, overnight moves, key events today. You read it on your phone over coffee.
09:30 AM
Market Opens. Alert state machine starts checking your 10 active setups every 15 minutes. You do nothing.
10:45 AM
CONFIRMED alert fires in #priority-alerts: "CF Oversold Bounce — All 4 conditions met." Entry $77.85, Stop $73.80, TP1 $83.00. You place the trade.
04:00 PM
Market Closes. Alert system pauses equity monitoring. Crypto alerts continue 24/7.
11:00 PM
Scanner Runs. New scan generates 10 A+ setups. Top 3 A++ posted to #priority-alerts. HTML article deployed to GitHub Pages. Monitoring alerts created for all 10.
11:05 PM
Done. You review tomorrow's setups before bed. Total active time today: ~15 minutes.

No charts to analyze. No screeners to run. No articles to write. The system does everything. You just follow the highest-conviction alerts.

Discord Bot Architecture

The bot is a Node.js application using discord.js v14. It's the bridge between Claude Code and your Discord server.

bot.js — Main Structure
const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');
const { exec } = require('child_process');
const schedule = require('./scheduler');
const config = require('./config.json');

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent
  ]
});

// Command handler
client.on('messageCreate', async (message) => {
  if (message.author.bot) return;
  if (!message.content.startsWith(config.prefix)) return;

  const args = message.content.slice(config.prefix.length).trim().split(/ +/);
  const command = args.shift().toLowerCase();

  switch (command) {
    case 'analyze':
      await handleAnalyze(message, args);
      break;
    case 'scan':
      await handleScan(message);
      break;
    case 'market':
      await handleMarketOverview(message);
      break;
    case 'alerts':
      await handleAlerts(message, args);
      break;
    case 'status':
      await handleStatus(message);
      break;
    case 'schedules':
      await handleSchedules(message);
      break;
    case 'run':
      await handleManualRun(message, args);
      break;
    case 'pause':
    case 'resume':
    case 'cancel':
      await handleScheduleControl(message, command, args);
      break;
  }
});

// How the bot invokes Claude Code
async function runClaudeCode(project, prompt) {
  return new Promise((resolve, reject) => {
    const cmd = `claude -p "${prompt}" --project /path/to/${project}`;
    const proc = exec(cmd, {
      maxBuffer: 10 * 1024 * 1024, // 10MB for large outputs
      timeout: 600000               // 10 min timeout
    });

    let output = '';
    proc.stdout.on('data', (data) => output += data);
    proc.stderr.on('data', (data) => console.error(`[stderr] ${data}`));
    proc.on('close', (code) => {
      if (code === 0) resolve(output);
      else reject(new Error(`Claude Code exited with code ${code}`));
    });
  });
}

// Scheduled task execution
async function executeScheduledTask(task) {
  const channel = client.channels.cache.get(config.channels.botLog);
  try {
    await channel?.send(`⏰ Starting scheduled task: **${task.command}**`);

    const result = await runClaudeCode(task.project, task.command);

    await channel?.send(`✅ Task completed: **${task.command}**`);

    // Post results to appropriate channel based on task type
    if (task.command.includes('daily')) {
      await postDailyBriefing(result);
    } else if (task.command.includes('scan')) {
      await postScannerResults(result);
    } else if (task.command.includes('retrospective')) {
      await postRetrospective(result);
    }

  } catch (error) {
    await channel?.send(`❌ Task failed: **${task.command}**\n\`\`\`${error.message}\`\`\``);
  }
}

client.login(config.token);

Process Management

ecosystem.config.js (PM2)
module.exports = {
  apps: [{
    name: 'claude-discord-bot',
    script: './bot.js',
    watch: false,
    autorestart: true,
    max_restarts: 10,
    restart_delay: 5000,
    env: {
      NODE_ENV: 'production',
      DISCORD_TOKEN: process.env.DISCORD_TOKEN
    }
  }]
};

// Start: pm2 start ecosystem.config.js
// Monitor: pm2 monit
// Logs: pm2 logs claude-discord-bot

Rich Discord Embeds

A++ Setup Alert Embed

🟢 CONFIRMED: CF Industries — Oversold Bounce [A+]

Fertilizer stock oversold on broad market selloff. Strong fundamentals, insider buying, sector tailwinds from energy rally.

Score
86.2 / 100
Entry
$76.50 – $78.50
Stop
$73.80 (-4.1%)
Target 1
$83.00 (+6.4%)
Target 2
$88.50 (+14.3%)
R/R
1.67
Regime
Early Risk-Off
Horizon
Swing 5-8 days
Embed Builder Code
function buildSetupEmbed(setup) {
  const color = setup.grade === "A++" ? 0xf59e0b :
                setup.grade === "A+"  ? 0x10b981 : 0x3b82f6;

  const emoji = setup.direction === "long" ? "🟢" : "🔴";

  return new EmbedBuilder()
    .setColor(color)
    .setTitle(`${emoji} CONFIRMED: ${setup.ticker} — ${setup.strategy} [${setup.grade}]`)
    .setDescription(setup.thesis)
    .addFields(
      { name: 'Score', value: `${setup.composite}/100`, inline: true },
      { name: 'Entry', value: `$${setup.entry}`, inline: true },
      { name: 'Stop', value: `$${setup.stop} (${setup.stopPct}%)`, inline: true },
      { name: 'Target 1', value: `$${setup.tp1} (+${setup.tp1Pct}%)`, inline: true },
      { name: 'Target 2', value: `$${setup.tp2} (+${setup.tp2Pct}%)`, inline: true },
      { name: 'R/R', value: `${setup.rr}`, inline: true },
      { name: 'Regime', value: setup.regime, inline: true },
      { name: 'Horizon', value: setup.horizon, inline: true },
      { name: 'Sector', value: `${setup.geo} ${setup.sector}`, inline: true }
    )
    .setThumbnail(`https://charts2.finviz.com/chart.ashx?t=${setup.ticker}&ty=c&ta=1&p=d&s=l`)
    .setFooter({ text: `DailyTickers Scanner | ${setup.date} | Confluence: ${setup.confluence}` })
    .setTimestamp();
}

Daily Briefing Embed

📊 Daily Briefing — Thursday March 6, 2026
Regime
⚠️ Early Risk-Off
VIX
23.75 (+8.2%)
SPY
$559.23 (-1.2%)
QQQ
$472.10 (-1.8%)
BTC
$87,450 (-0.5%)
GLD
$285.30 (+1.2%)
Key Event
Nonfarm Payrolls 8:30 AM
Active Alerts
10 watching, 2 near-confirmed

Scheduling System

schedules.json
[
  {
    "id": 1,
    "name": "Daily Briefing",
    "schedule": "every day at 07:00",
    "command": "analyse daily",
    "project": "articles",
    "enabled": true,
    "timezone": "Europe/Paris",
    "channels": ["#market-briefing"],
    "lastRun": "2026-03-05T07:00:00Z",
    "nextRun": "2026-03-06T07:00:00Z"
  },
  {
    "id": 2,
    "name": "Daily Scanner",
    "schedule": "every weekday at 23:00",
    "command": "scan du jour",
    "project": "articles",
    "enabled": true,
    "timezone": "America/New_York",
    "channels": ["#trading-signals", "#priority-alerts"],
    "lastRun": "2026-03-05T23:00:00Z",
    "nextRun": "2026-03-06T23:00:00Z"
  },
  {
    "id": 3,
    "name": "Weekly Retrospective",
    "schedule": "every friday at 23:00",
    "command": "rétrospective scanner",
    "project": "articles",
    "enabled": true,
    "channels": ["#performance"],
    "lastRun": "2026-02-28T23:00:00Z",
    "nextRun": "2026-03-07T23:00:00Z"
  },
  {
    "id": 4,
    "name": "Weekly Report",
    "schedule": "every sunday at 18:00",
    "command": "nouvelle analyse weekly",
    "project": "articles",
    "enabled": true,
    "channels": ["#weekly-report"]
  }
]

Discord Commands for Schedule Management

// List all schedules
!schedules
→ #1 Daily Briefing    | every day at 07:00    | ✅ Enabled | Next: Mar 6 07:00
  #2 Daily Scanner     | weekday at 23:00      | ✅ Enabled | Next: Mar 6 23:00
  #3 Weekly Retro      | friday at 23:00       | ✅ Enabled | Next: Mar 7 23:00
  #4 Weekly Report     | sunday at 18:00       | ✅ Enabled | Next: Mar 8 18:00

// Force-run a task now
!run #2
→ ⏰ Starting: Daily Scanner (manual trigger)

// Pause a task
!pause #2
→ ⏸️ Paused: Daily Scanner

// Resume
!resume #2
→ ▶️ Resumed: Daily Scanner

The Daily Pipeline — Step by Step

Here's exactly what happens when the 23:00 scanner task fires:

Scanner Pipeline (12 steps)
// Step 1: Read retrospective feedback
const retros = readAllRetrospectives("scanner/retrospective/");
const adjustedWeights = computeAdjustedWeights(retros);
// Output: strategy weights modified by historical performance

// Step 2: Read last 3 scans for anti-duplicate check
const recentTickers = readRecentScans(3);
// Output: Set of tickers to avoid (70% must be new)

// Step 3: Regime detection
const overview = await GetMarketOverview({ depth: "deep" });
const regime = detectRegime(overview);
// Output: "EARLY_RISK_OFF", strategy weights

// Step 4: Multi-strategy screening
const autoResults = await RunAutoScreener();
const oversold = await RunScreener({
  conditions: "rsi14 < 30 && vol > sma(vol,20) * 2 && close > sma(close,200)",
  universe: "SP500"
});
const momentum = await RunScreener({
  conditions: "close > max(high,20d) && vol > avgVol * 1.5 && rsi14 > 55",
  universe: "SP500"
});
const breakout = await RunScreener({
  conditions: "bbWidth < 5 && close > sma(close,200)",
  universe: "SP500"
});
// Output: ~50 unique candidates

// Step 5: EU/APAC/ETF screening
const euApac = await RunScreener({
  conditions: "rsi14 < 40 && close > ema(close,50)",
  symbols: ["VGK","EWG","EWQ","SAP","ASML","EWJ","EWY","FXI"]
});
const etfs = await RunScreener({
  conditions: "change(close,5d) > 2 && vol > avgVol",
  symbols: ["XLF","XLE","XLK","XLV","GLD","SLV","TLT","ARKK"]
});
// Output: ~10 additional candidates

// Step 6: Deep data collection
const deepData = await QueryData({
  symbols: allCandidates,
  types: "quote,technicals,support_resistance,sentiment_overall," +
         "insider_transactions,financials,news,trading_signals"
});
// Output: Full data for 60 candidates

// Step 7: P0 validation
const validated = allCandidates.filter(t => {
  const spot = deepData[t].quote.price;
  const entry = computeEntry(deepData[t]);
  return Math.abs(spot - entry) / spot < 0.10; // Within 10%
});
// Output: ~55 validated candidates

// Step 8: Multi-layer scoring (Part 3)
const scored = validated.map(t => scoreAllLayers(t, deepData[t], regime));
// Output: Each candidate has composite score + 6 layer scores

// Step 9: Filter and diversify
const topCandidates = scored
  .filter(s => s.composite >= 85 && s.failedLayers.length === 0)
  .sort((a, b) => b.composite - a.composite);

const final10 = diversify(topCandidates, {
  minUS: 5, minEU: 2, minAPAC: 1, minETF: 2,
  minDefensive: regime.includes("RISK_OFF") ? 2 : 0
});
// Output: 10 diversified A+ setups

// Step 10: A++ re-rank (top 3)
const top3 = final10.slice(0, 3);
// These go to #priority-alerts

// Step 11: Generate HTML + deploy
await generateScannerArticle(final10, regime, overview);
await exec("node tools/add_card.js scanner/YYYYMMDD/index.html");
await exec("git add scanner/ data/ && git commit && git push");
// Output: Article live on GitHub Pages

// Step 12: Post to Discord + create alerts
for (const setup of top3) {
  await postToChannel("#priority-alerts", buildSetupEmbed(setup));
}
for (const setup of final10) {
  await postToChannel("#trading-signals", buildSetupSummary(setup));
  await createMonitoringAlert(setup); // Part 4 alert system
}

The A++ Selection Funnel

From 500+ stocks in the universe down to 3 A++ trades — here's the funnel:

StageFilterCandidatesReduction
1. UniverseRunAutoScreener + 3 DSL strategies~500 → 6088%
2. P0 ValidationPrice within 10% of spot, not halted60 → 558%
3. Multi-Layer ScoreComposite ≥ 85, no failed layers55 → 2064%
4. DiversificationSector/geo/strategy mix requirements20 → 1240%
5. Anti-Duplicate70% must be new vs last 3 scans12 → 1017%
6. AI Re-RankTop 3 by conviction + R/R + uniqueness10 → 370%
500+
Universe
10
A+ Setups
3
A++ Elite
0.6%
Selection Rate

Why Only 3?

Retail traders lose money by over-trading. By limiting to 3 A++ setups per day, you force extreme selectivity. Each trade has maximum conviction across all 6 scoring layers. Quality over quantity. If none qualify as A++, the system reports 0 trades — and that's perfectly fine.

Monitoring & Reliability

Health Check System

!status command output
📊 Bot Status
━━━━━━━━━━━━━━━━━━━━━━
Uptime:         12d 4h 32m
Last Scan:      Mar 5 23:00 ✅
Last Briefing:  Mar 6 07:00 ✅
Last Retro:     Feb 28 23:00 ✅

MCP Servers:
  dailytickers:  🟢 Connected (latency: 45ms)
  market-alerts: 🟢 Connected (latency: 12ms)

Active Alerts:  10 watching, 2 near-confirmed, 0 confirmed
Errors (24h):   0
Disk Usage:     2.3 GB / 10 GB
Git Status:     clean, up to date with origin/main

Error Handling Strategy

Error TypeActionNotification
MCP connection failureRetry 3x with exponential backoff#bot-logs warning
Claude Code timeout (10min)Kill process, retry once#bot-logs error
Git push failureStash, pull, reapply, retry#bot-logs error
Empty scanner resultsLoosen DSL thresholds by 10%#bot-logs warning
HTML < 10KBAbort push, log error#bot-logs critical
add_card.js failureAbort push, manual fix needed#bot-logs critical + @admin

Security Checklist

Part 6 of 6 — Final
The Feedback Loop — Self-Improving Intelligence
Automated Trading with Claude Code 5/6