Feature: Trading Signal Generation
Relevant source files
The following files were used as context for generating this wiki page:The Trading Signal Generation feature is a core component of the OnChain Sage platform. Its primary purpose is to process and aggregate raw data from multiple sources, such as social sentiment and on-chain market metrics, to produce actionable trading signals. These signals are then stored, ranked, and exposed via a backend API for consumption by the frontend dashboard.
This system is composed of two main parts: a Python-based data processing layer for signal creation and a NestJS-based backend for serving, ranking, and managing the signals. The process is designed to be data-driven, modular, and scalable, with caching mechanisms to ensure responsive delivery of signals to the user.
Sources: ARCHITECTURE.md, apps/data-processing/processors/signal_aggregator.py, apps/backend/src/signals/signals.service.ts
Architecture Overview
The signal generation and delivery process follows a multi-layered architecture. It begins with data ingestion and processing in the Python environment, which generates the core signals. These signals are then persisted and made available through the backend, which adds a layer of ranking, caching, and real-time distribution.
The following diagram illustrates the high-level data flow for the Trading Signal Generation feature.
This diagram shows data flowing from raw sources through the SignalAggregator, into the database, and finally being served by the backend API and WebSockets to the user interface.
Sources: ARCHITECTURE.md, apps/data-processing/processors/signal_aggregator.py, apps/backend/src/signals/signals.service.ts, apps/backend/src/tasks/signal.task.ts
Data Processing: SignalAggregator
The SignalAggregator class, located in the data processing layer, is responsible for the initial creation of trading signals. It takes sentiment data and market metrics as input, normalizes them, and computes an aggregate score to generate a categorized signal.
Sources: apps/data-processing/processors/signal_aggregator.py
Signal Generation Flow
The generate_signal method orchestrates the entire process within the aggregator.
This flowchart shows the step-by-step logic executed by the generate_signal method to produce a single, comprehensive trading signal from raw inputs.
Sources: apps/data-processing/processors/signal_aggregator.py:270-316
Core Logic
1. Normalization
- Sentiment Data: The
normalize_sentiment_datamethod converts raw sentiment data (e.g., text, likes, retweets) into a normalizedsentiment_scorebetween 0 and 1. If a score is not present, it performs a simple keyword-based analysis as a placeholder for a more complex NLP model. - Market Metrics: The
normalize_market_metricsmethod converts raw market data like liquidity, volume, and price change percentage into normalized scores between 0 and 1. For example,price_change_pctis mapped to aprice_actionscore.
Sources: apps/data-processing/processors/signal_aggregator.py:68-175
2. Score Calculation
The calculate_aggregate_score method combines the normalized sentiment score and market metrics using a weighted formula. The weights are configurable.
Sources: apps/data-processing/processors/signal_aggregator.py:177-202
3. Confidence and Categorization
- Confidence: The
determine_confidencemethod assigns a "high", "medium", or "low" confidence level. Confidence is high when the aggregate score is high and indicators (like sentiment and price action) are aligned. It is low when indicators are contradictory. - Category: The
categorize_signalmethod classifies the signal as "strong_buy", "buy", "neutral", "sell", or "strong_sell" based on configurable thresholds for the aggregate score, sentiment, and price action.
Sources: apps/data-processing/processors/signal_aggregator.py:204-268
Configuration
The SignalAggregator is initialized with a default configuration that defines the weights and thresholds used in the generation process.
| Parameter | Default Value | Description |
|---|---|---|
sentiment_weight | 0.4 | The weight of the sentiment score in the aggregate calculation. |
liquidity_weight | 0.2 | The weight of the normalized liquidity score. |
volume_weight | 0.2 | The weight of the normalized volume score. |
price_action_weight | 0.2 | The weight of the normalized price action score. |
high_threshold | 0.7 | The score threshold for a signal to be considered for "high" confidence. |
medium_threshold | 0.4 | The score threshold for a signal to be considered for "medium" confidence. |
categories | (object) | Defines min/max scores for strong_buy, buy, neutral, sell, strong_sell. |
Sources: apps/data-processing/processors/signal_aggregator.py:23-40
Backend Signal Management
The backend, built with NestJS, is responsible for storing, managing, ranking, and serving the signals generated by the data processing layer.
Signal Data Model
The Signal entity defines the structure of a trading signal as stored in the PostgreSQL database.
ER Diagram
This diagram shows the schema for the signal table in the database.
Sources: apps/backend/src/signals/entities/signal.entity.ts
Entity Fields
| Field | Type | Description |
|---|---|---|
signal_id | number (PK) | Unique identifier for the signal. |
signal_type | string | The type of signal (e.g., 'price_movement', 'volume_spike'). |
value | string | The value associated with the signal (e.g., price target, percentage change). |
status | SignalStatus (enum) | The current status of the signal (ACTIVE, PENDING, SUCCESSFUL, FAILED, EXPIRED). |
confidence_level | string | The confidence level of the signal ('HIGH', 'MEDIUM', 'LOW'). |
historical_performance | jsonb | An object containing metrics like success_rate, total_signals, etc. |
is_verified | boolean | A flag indicating if the signal has been verified. |
timestamp | Date | The creation or last update time of the signal. |
Sources: apps/backend/src/signals/entities/signal.entity.ts:6-54
API Endpoints
The SignalsController exposes REST endpoints for interacting with trading signals.
| Method | Path | Description |
|---|---|---|
GET | /signals | Retrieves a paginated list of all active signals. |
GET | /signals/top | Retrieves a paginated list of top signals, ranked by a weighted algorithm. Can be filtered by type. |
GET | /signals/:signal_id | Retrieves a single signal by its ID. |
PATCH | /signals/:signal_id/restore | Restores an expired signal to active status. |
PATCH | /signals/:signal_id/extend | Extends the lifetime of a signal by updating its timestamp. |
Sources: apps/backend/src/signals/signals.controller.ts
SignalsService
This service contains the core business logic for managing signals.
Signal Ranking
The getTopSignals method retrieves and ranks signals to identify the most relevant ones. The ranking is performed by the private rankSignals method, which calculates a weight for each signal based on its recency and reputation.
The ranking algorithm uses the following formula:
weight = (reputationScore * reputationWeight) + (recencyScore * recencyWeight)
- Recency Score: Calculated using an exponential decay function:
Math.exp(-decayFactor * ageInHours). Signals lose value as they get older. - Reputation Score: Derived from the signal's
historical_performance.success_rateor, if unavailable, mapped from itsconfidence_level.
Sources: apps/backend/src/signals/signals.service.ts:148-185
Caching Strategy
To improve performance, the SignalsService heavily utilizes a Redis cache.
findAll: Caches the list of all signals under the keysignals:all.getTopSignals: Caches ranked signals under a dynamic key likesignals:top:<filter>.- Invalidation: The cache is invalidated by the
invalidateCacheandinvalidateTopSignalsCachemethods. These are called when signals are updated (e.g.,restoreSignal,extendSignal) or by scheduled tasks.
Sources: apps/backend/src/signals/signals.service.ts:13-19, 29-49, 81-146, 187-204
Scheduled Tasks (SignalTask)
The SignalTask class defines cron jobs related to signal management.
checkExpiredSignals: Runs daily at midnight (CronExpression.EVERY_DAY_AT_MIDNIGHT) to mark old signals asEXPIREDbased on a configurable number of days.generateMockSignal: Runs every minute (CronExpression.EVERY_MINUTE) to generate a new mock signal for development and testing purposes. After creating a signal, it broadcasts it via theSignalGateway(WebSocket) and invalidates the top signals cache to ensure the new signal is included in the ranking.
Sources: apps/backend/src/tasks/signal.task.ts
Conclusion
The Trading Signal Generation feature is a sophisticated system that transforms raw market and social data into ranked, actionable insights. It leverages a Python-based SignalAggregator for initial processing and a robust NestJS backend that provides ranking, caching, and real-time distribution via REST APIs and WebSockets. The scheduled tasks ensure the system remains up-to-date by managing signal lifecycles and invalidating caches, delivering a responsive and relevant experience to the end-user.