# How the Signal Router Combines Multiple Data Sources into Edge Signals for Trading

> Discover how the Signal Router combines multiple data sources into edge signals for trading. Learn about prediction aggregation, real-time enrichment, and order execution with risk controls.

- Repository: [AL/CloddsBot](https://github.com/alsk1992/CloddsBot)
- Tags: how-to-guide
- Published: 2026-09-14

---

**The Signal Router aggregates predictions from the TradingSignal bus, enriches them with real-time market features and optional ML confidence scoring, then validates and executes orders through a serial pipeline that enforces risk controls like cooldowns and daily loss limits.**

The signal router serves as the central intelligence hub in the alsk1992/CloddsBot repository, transforming raw market predictions into executable edge signals for the trading layer. By fusing data from prediction models, live order books, machine learning classifiers, and dynamic risk configurations, it ensures only validated, properly-sized orders reach the execution service.

## Architecture of the Signal Router

At its core, the router is implemented in [`src/signal-router/router.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/signal-router/router.ts) and exposes an EventEmitter-based API that includes `start`, `stop`, and `getStats` methods. The `createSignalRouter` factory function receives dependencies including the `ExecutionService`, `SignalRouterConfig`, optional `SmartRouter`, and optional `MLSignalModel`. This dependency injection pattern allows the router to remain stateless while coordinating between disparate data sources.

## The Nine-Stage Data Integration Pipeline

The router processes every incoming signal through a strict nine-stage pipeline, pulling from different data sources at each step to build a complete execution context.

### 1. Signal Intake from the Trading Bus

The router registers a listener on the `SignalBus` via `signalBus.onSignal` upon initialization. When prediction models or market-making bots publish signals, the router immediately enqueues them in the `signalQueue` for serial processing. This intake mechanism ensures that high-frequency signals do not overwhelm the execution layer.

### 2. Real-Time Feature Enrichment

For each dequeued signal, the router calls `getMarketFeatures(platform, marketId, outcomeId)` from the feature-engineering service ([`src/services/feature-engineering/index.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/services/feature-engineering/index.ts)). This function fetches the latest order-book depth, liquidity scores, spread percentages, and tick data. The resulting metrics populate a `features` object that travels with the signal through subsequent stages.

### 3. ML Confidence Modulation

When an `mlModel` is supplied, the router converts the raw feature set into a training-compatible structure using `combinedToMarketFeatures(features)` from [`src/ml-pipeline/trainer.js`](https://github.com/alsk1992/CloddsBot/blob/main/src/ml-pipeline/trainer.js). It then queries the model via `mlModel.predict`. The prediction result performs dual duty: it can reject signals where ML confidence strongly disagrees with the raw signal, and it rescales signal strength using the formula `signal.strength *= (0.5 + 0.5 * mlSignal.confidence)`. This modulation turns raw predictions into weighted edge signals based on historical model accuracy.

### 4. Smart Router Integration

If `cfg.useSmartRouter` is enabled, the prepared order request is handed to the `SmartRouter` for additional aggregation or routing logic before reaching the execution service. This layer allows for complex order splitting across multiple venues or liquidity pools.

### 5. Risk Validation and Filtering

The `shouldRoute` function validates the enriched signal against guardrails defined in `SignalRouterConfig`. It checks signal type filters, neutral direction constraints, minimum strength thresholds, platform whitelists, market exclusion lists, per-market cooldowns (`marketCooldowns`), daily P&L limits (`maxDailyLoss`), and maximum concurrent position counts. Signals failing any check are logged as `rejected` with a specific `skip` reason recorded for audit trails.

### 6. Price Selection from Market Data

Using the order-book data retrieved in stage two, the router selects the best ask for buy orders and the best bid for sell orders. If only tick data is available, it falls back to the tick price. Signals lacking usable price data are rejected at this stage to prevent malformed orders.

### 7. Dynamic Position Sizing

The final order size is computed as `defaultSizeUsd * signal.strength` when `strengthScaling` is enabled in configuration. The result is capped to `maxSizeUsd` and rounded to a minimum of $1. This ensures position sizes remain proportional to signal confidence while respecting account risk limits.

### 8. Order Execution

For live trades (when `dryRun` is false), the router constructs an `orderRequest` and calls the appropriate method on the injected `ExecutionService`—such as `execution.makerBuy` or `execution.marketSell`. The order mode (`maker`, `market`, or `limit`) determines the specific execution path. Successful executions emit an `executed` event containing the `orderId` and market details; failures emit a `failed` event with error context.

### 9. Post-Execution State Management

After successful execution, the router places the market key on a cooldown map using `marketCooldowns.set` to prevent immediate re-entry. It updates the `openPositions` tracking map, increments daily statistics, and appends the execution record to `recentExecutions`. This housekeeping ensures that subsequent signals respect the updated portfolio state.

## Serial Queue Processing

Signals are processed one-by-one via the `drainQueue` mechanism to avoid concurrent fan-out. This serial approach guarantees that each market’s cooldown timers and position limits are evaluated against the most current state, preventing race conditions that could lead to over-leveraging or duplicate orders.

## Configuration and Runtime Control

The router supports runtime reconfiguration through the `updateConfig` method, allowing operators to toggle `dryRun` mode, adjust strength thresholds, enable or disable the smart router, or modify size limits without restarting the bot. When `dryRun` is active, the router emits `dry_run` events instead of calling execution methods, logging how the signal would have been executed—essential for back-testing and safe deployment.

## Practical Implementation Example

```typescript
// Create a router instance (in gateway/index.ts)
import { createSignalRouter } from './signal-router';

const router = createSignalRouter(
  executionService,          // ExecutionService instance
  routerConfig,              // SignalRouterConfig from config file
  smartRouter,               // Optional SmartRouter
  mlModel,                   // Optional MLSignalModel
);

// Start the router with the signal bus (provided by the ML pipeline)
router.start(signalBus);

// Listen for execution events (useful for logging or UI updates)
router.on('executed', (exec) => {
  console.log(`Order ${exec.orderId} executed for ${exec.signal.marketId}`);
});

router.on('dry_run', (exec) => {
  console.log(`Dry-run: would have placed ${exec.orderSize} USD @ ${exec.orderPrice}`);
});

```

## Summary

- The signal router in [`src/signal-router/router.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/signal-router/router.ts) orchestrates nine distinct stages to combine prediction signals, market features, and ML insights into executable trades.
- It enforces strict risk controls through the `shouldRoute` validator, checking cooldowns, daily loss limits, and position counts before execution.
- ML confidence modulates signal strength dynamically, allowing the router to scale position size based on model agreement.
- Serial queue processing via `drainQueue` prevents concurrent execution errors and ensures accurate state management.
- Runtime configuration updates and dry-run mode enable safe testing and operational flexibility without system restarts.

## Frequently Asked Questions

### How does the signal router handle conflicting signals from multiple data sources?

The router resolves conflicts through sequential validation and ML modulation. Raw signals from the TradingSignal bus are enriched with market features, then optionally scored by an ML model. If the ML prediction strongly disagrees with the raw signal direction, the router rejects the trade. This hierarchy ensures that algorithmic predictions are weighted against statistical model confidence before reaching the execution layer.

### What happens when the ML model confidence is low?

When `mlModel.predict` returns low confidence, the router applies the rescaling formula `signal.strength *= (0.5 + 0.5 * mlSignal.confidence)`, which reduces the position size proportionally. In extreme cases where confidence indicates a likely false positive, the signal may be rejected entirely during the validation stage, preventing low-probability trades from reaching the market.

### Can the router operate in simulation mode before live trading?

Yes. The router defaults to `dryRun` mode, where it calculates order prices and sizes but emits `dry_run` events instead of calling `ExecutionService` methods. This mode allows strategy validation against historical or real-time data without capital risk. Operators can toggle live trading via `updateConfig` or by modifying the initial `SignalRouterConfig` passed to `createSignalRouter`.

### Which file contains the core routing logic?

The primary implementation resides in [`src/signal-router/router.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/signal-router/router.ts), which contains the `createSignalRouter` factory and the `drainQueue` processing loop. Type definitions for configurations and execution events are located in [`src/signal-router/types.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/signal-router/types.ts), while the feature enrichment logic used by the router is implemented in [`src/services/feature-engineering/index.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/services/feature-engineering/index.ts).