Strategy and Signal System
Core technical engine for condition evaluations, visual strategy compiling, indicators calculation, and orderbook stop-loss adaptation inside DepthSight.
The Strategy and Signal System is the decision-making core of DepthSight — the layer where raw market data is transformed into actionable trading signals. At its heart lies a foundation-based architecture where independent evidence modules (orderbook density, candlestick patterns, trend state, tape acceleration, and more) each contribute weighted "conviction" to a final go/no-go decision. The system is designed around a dual-evaluation contract: every condition must be evaluable both as a scalar value (for live, tick-by-tick trading) and as a vectorized Pandas Series (for backtesting over entire price histories), ensuring parity between what you simulate and what you trade.
Core Signal Datatypes
Every strategy ultimately produces a StrategySignal — a validated, self-describing dataclass that carries not just direction and price levels, but also risk parameters, partial exit targets, and execution mode. The signal is the contract boundary between the strategy layer and the trading controller: once a StrategySignal is emitted, the controller treats it as immutable instructions.
| Datatype | Purpose | Key Fields |
|---|---|---|
| SignalDirection | Enum: LONG, SHORT, NEUTRAL | Used for signal direction and trend classification |
| OrderMode | Enum: MARKET, LIMIT_BREAK, LIMIT_RETEST | Determines execution semantics |
| StrategySignal | Complete trade instruction | direction, stop_loss, take_profit, entry_price, mode, partial_targets, risk_pct, risk_usd, no_stop_loss |
| PartialTarget | Fractional exit at a specific price | price, fraction ($0 < f ≤ 1.0$) |
| DensityInfo | Orderbook density cluster | price, size_usd, distance_from_current_price_abs, side |
| OrderbookAnalysisResult | Aggregated orderbook state | nearest_support, nearest_resistance, is_price_near_support, is_price_near_resistance |
The StrategySignal.__post_init__ method enforces strict directional validation: for LONG signals, stop-loss must be below the comparison price and take-profit above it; for SHORT, the inverse. Partial targets must be monotonically ordered in the direction of trade, and their fractions must not exceed 1.0. If partials sum to less than 1.0, a final take_profit is mandatory. If stop_loss is None, the signal enters no-stop mode (used by DCA/Grid strategies), and no_stop_loss is auto-set to True.
The Foundation System
Foundations are the atomic evidence units that a strategy evaluates independently before combining their results. Each foundation type checks a specific aspect of market conditions and returns a (passed: bool, details: dict) tuple. The strategy then aggregates these results using configurable weights and thresholds to determine whether overall conviction is sufficient to emit a signal.
The system defines nine foundation types, each implemented as a _check_foundation_* function in strategy.py:
| Foundation Type | Constant | What It Evaluates |
|---|---|---|
| Orderbook | FOUNDATION_ORDERBOOK | Bid/ask density clusters near current price; support/resistance proximity; approach detection |
| Pattern | FOUNDATION_PATTERN | Classic candlestick patterns: engulfing, pin bar, doji, inside bar |
| Trend | FOUNDATION_TREND | SMA crossover + RSI direction alignment |
| Level | FOUNDATION_LEVEL | Significant price levels (daily/weekly high/low, local swing points) |
| Round Number | FOUNDATION_ROUND_NUMBER | Psychological price levels based on configurable step definitions |
| Tape | FOUNDATION_TAPE_ACCELERATION | Time-and-sales delta, buy/sell ratio, acceleration metrics |
| Market Activity | FOUNDATION_MARKET_ACTIVITY | Trading session and volume activity filters |
| Volume Confirmation | FOUNDATION_VOLUME_CONFIRMATION | Relative volume thresholds and NATR filters |
| Return to Level | FOUNDATION_RETURN_TO_LEVEL | Price returning to a previously identified significant level |
Orderbook Foundation Deep Dive
The orderbook foundation is the most computationally intensive module. It operates on two depth data sources — depth_trading (raw exchange orderbook) and depth_analysis (aggregated/processed depth) — and performs conflict detection between them. If a support density from trading data is within conflict_ticks of a resistance density from analysis data, the support is invalidated (and vice versa), preventing the strategy from acting on contradictory orderbook signals.
The low-level density search uses Numba JIT compilation when available (_first_density_idx_numba), falling back to a pure-Python implementation (_first_density_idx_py). This dual-path ensures correctness in all environments while leveraging hardware acceleration in production.
Pattern Foundation
The pattern foundation detects five classic candlestick formations through a dispatcher pattern (CLASSIC_PATTERN_CHECKS). Each pattern checker operates on a specific index of the OHLCV DataFrame, enabling multi-timeframe pattern detection by simply passing different DataFrames:
- Bullish/Bearish Engulfing: Current candle's body completely engulfs the previous candle's body, with directional close requirements.
- Pin Bar: Lower or upper wick exceeds 50% of total candle range with body < 33% of range and opposing wick smaller than body.
- Doji: Body is less than 10% of the total candle range.
- Inside Bar: Current candle's high-low range is entirely contained within the previous candle's range.
Level and Round Number Foundations
Significant levels are resolved through a priority cascade across timeframes. For example, daily_high is first attempted from the 1D kline (1 candle), then 4H (6 candles), then 1H (24 candles) — the first available source wins. This ensures level detection degrades gracefully when higher-timeframe data is unavailable.
Round number levels use a Decimal-precision step calculation that snaps psychological price boundaries to the instrument's tick size. Step definitions are price-range-dependent: different steps lists apply depending on the current price level, and each step is quantized to an exact multiple of tick_size before generating candidate levels within ±max_check_per_step_type steps of the current price.
Condition Core: Unified Evaluation Engine
The condition_core module is the single source of truth for all condition evaluation logic. It enforces a strict separation between pure evaluation functions (scalar, testable in isolation) and vectorized evaluators (Pandas-native, for backtesting). This eliminates the parity bugs that arise when backtesting and live trading use different code paths.
Condition Type Taxonomy
The module maintains a canonical CONDITION_TYPE_ALIASES map that normalizes legacy genetic optimizer names to canonical types (e.g., "stoch_condition" $\to$ "stochastic_condition"). This ensures backward compatibility when loading strategies produced by the genetic breeder.
| Canonical Type | Operators / Modes | Source Data |
|---|---|---|
| stochastic_condition | gt, lt, cross_above, cross_below | %K / %D values |
| bollinger_bands_condition | price_below_lower, price_above_upper, width_gt, width_lt | BB lower/upper/bandwidth |
| macd_condition | crossover, cross_below_signal, hist_gt_zero, hist_lt_zero, value_above, value_below | MACD line / signal / histogram |
| rsi_condition | gt, lt, gte, lte | RSI value vs threshold |
| ma_cross_condition | crosses_above, crosses_below, above, below | Fast EMA vs slow EMA |
| trend_direction | long, short, any_trend, flat | SMA cross + RSI bounds |
| value_comparison | gt, lt, gte, lte, cross_above, cross_below | Dynamic operand pairs (candle, indicator, constant) |
| tape_condition | gt, lt, gte, lte | Delta volume/count, buy/sell ratio, acceleration |
| time_filter | include, exclude | UTC hour range |
| natr_filter | gt, lt | NATR value vs threshold |
| adx_filter | gt, lt | ADX value vs threshold |
| volatility_filter | gt, lt | (High-Low)/Close ratio |
| trading_session | include, exclude | Named session windows |
| btc_state_filter | State comparison | BTC trend state |
| price_consolidation | Range/ATR ratio | Squeeze detection |
| open_interest | gt, lt | OI change metrics |
| correlation | Threshold comparison | Cross-asset correlation |
Scalar vs. Vectorized Contract
Every condition type exposes two evaluation paths:
- Scalar functions (
evaluate_*_logic): Accept pre-calculated float values, returnbool. Used byBaseStrategyin live trading where indicators are already computed per-tick. - Vectorized functions (
evaluate_*_vectorized): Acceptmain_dfandsignals_dfDataFrames, returnpd.Series[bool]. Used byFastVectorBacktesterfor batch evaluation across entire histories.
The scalar wrappers in condition_core.py (e.g., evaluate_stochastic_scalar) bridge the gap for VisualBuilderStrategy by computing indicators on a provided DataFrame slice and extracting the last bar's result through the corresponding vectorized path.
[!WARNING] When adding a new condition type, you must implement both the scalar
_logicfunction and the_vectorizedfunction, then register the type inCONDITION_TYPE_ALIASES. TheFastVectorBacktesterexclusively calls vectorized evaluators, whileBaseStrategy.check_signalcalls scalar logic — a mismatch between these two paths is the most common source of backtest-vs-live parity bugs.
Compiled Condition Trees
Strategies can represent their entry/exit logic as a tree of CompiledConditionNode instances. Each node is a frozen dataclass carrying:
node_id: Unique identifier for traceabilitynode_type: The condition type (mapped throughnormalize_condition_type)params: Condition-specific parametersanalysis_level: Either"minute_bar_filter"or"second_bar_trigger", controlling when the condition is evaluated in the controller's two-phase signal detectionchildren: Tuple of child nodes (for logical AND/OR grouping)checker/evaluator: Optional callable for custom evaluation
This tree structure enables the visual strategy builder to compose complex logical expressions (e.g., "RSI < 30 AND (MACD crossover OR Bollinger below lower)") as a nested condition tree that the backtester can walk recursively.
CompassStrategy: ML-Driven Signal Generation
CompassStrategy is a fundamentally different signal source — instead of rule-based foundations, it uses an XGBoost classifier trained on orderbook pressure, tape delta, and volatility features, gated by an Oracle regime filter that prevents trading during adverse market states.
The pipeline executes in strict order:
- Data validation: Requires at least 60 bars of 1m kline data for Oracle volatility calculations.
- Oracle regime check: A Gaussian Mixture Model classifies market into "Amnesia" (regime 1 = safe to trade) vs "Paranoia" (regime 0 = avoid trading) using three engineered sensors.
- Compass feature extraction: Eight features including
pressure_buy,pressure_sell,absorption,path_resistance,obi_1p,delta_wall_divergence,scalper_natr, andrelative_volume. - XGBoost prediction: Supports both multiclass output (3-class: SHORT/SKIP/LONG) and binary output, with configurable
min_entry_probabilitythreshold. - Risk parameterization: SL/TP distances are calculated as ATR multipliers (default: 1.5× ATR for SL, 7.5× ATR for TP), with optional partial exits at specified R:R multiples and automatic move-to-breakeven on first TP hit.
Oracle Sensors
The Oracle's regime detection relies on three engineered features computed from kline history:
| Sensor | What It Measures | Computation |
|---|---|---|
| Memory (Forgetting Speed) | Short-term vs long-term volatility ratio | vol_60 / vol_1440 of log returns |
| News Background (Asymmetry) | Sentiment-weighted news impact | (positive - negative) * (important + 1) rolling 720-bar mean; neutral 0.0 if sentiment data unavailable |
| Complexity (Drift) | ATR normalized by price | EMA-14 ATR / Close |
The Oracle caches its regime prediction, recalculating only when a new closed candle timestamp is detected — an optimization that avoids redundant GMM inference on every tick.
Signal Validation and Stop-Loss Adaptation
StrategySignal Validation
The StrategySignal dataclass enforces comprehensive validation at construction time. Beyond the basic directional SL/TP checks, it validates:
- Limit mode requires explicit
entry_price:MARKETmode usestrigger_priceas the comparison reference; LIMIT modes (LIMIT_BREAK,LIMIT_RETEST) require a positiveentry_price. - Partial target ordering: Targets must be monotonically increasing (
LONG) or decreasing (SHORT) in price, with fractions summing to ≤ 1.0. - Coverage completeness: If partial targets sum to exactly 1.0, the final
take_profitis effectively superseded (a warning is logged); if they sum to less than 1.0, atake_profitmust be provided for the remaining fraction.
Orderbook-Adapted Stop-Loss
When ADAPT_SL_TO_ORDERBOOK_ENABLED is true, the system can adjust stop-loss placement to sit behind significant orderbook density walls. The adaptation logic (_adapt_sl_to_orderbook) works as follows:
- Identify the nearest density cluster on the protective side (bid density for LONG SL, ask density for SHORT SL).
- Verify the density is sufficiently far from entry (
ORDERBOOK_ADAPT_MIN_DENSITY_DISTANCE_ATR). - Place the adapted SL
ORDERBOOK_ADAPT_SL_TICKS_BEHIND_DENSITYticks behind the density wall. - Reject if the adapted SL would widen the original SL by more than
ORDERBOOK_ADAPT_MAX_OFFSET_ATR$\times$ ATR. - For LONG: only adapt if the candidate SL is tighter (closer to entry) than the original; never widen risk.
A corresponding _adapt_tp_to_orderbook function applies analogous logic for take-profit, placing TP just before resistance density clusters.
[!IMPORTANT] The orderbook adaptation is a risk-tightening mechanism, not a risk-widening one. It will only move your stop-loss closer to entry (reducing risk per trade) or leave it unchanged. If you need to widen SL based on orderbook structure, you must implement that at the strategy level before signal emission.
Architectural Summary
The Strategy and Signal System operates on three principles that make it maintainable and extensible:
- Separation of evaluation from aggregation:
condition_core.pyknows nothing about foundations or signal assembly — it only evaluates individual conditions.strategy.pyknows nothing about how MACD is calculated — it only consumes the boolean result. This separation means you can add new indicator types without touching the strategy layer. - Scalar-vectorized parity: Every condition has two evaluation paths that must produce identical results for identical inputs. The backtester uses vectorized paths for speed; the live trader uses scalar paths for tick-by-tick evaluation. Deviations between these paths are caught by the parity test suite.
- Foundation composability: Each foundation is an independent, testable module that contributes weighted evidence. New foundations can be added by implementing a
_check_foundation_*function and registering its constant — the existing aggregation logic requires no modification.
From here, the emitted StrategySignal flows into the Trading Controller Lifecycle, where it is validated against risk limits, position sizing, and execution constraints. For understanding how these signals are evaluated over historical data, see Dual Backtesting Engines. For the ML-specific feature extraction pipeline, see ML Pipeline and Compass Strategy.
Architecture Overview
Deep dive into the 8 containerized services, system topology, data flows, and design patterns of DepthSight.
Trading Controller Lifecycle
Detailed analysis of the initialization, state management, position lifecycle, event processing loop, and graceful shutdown inside the core TradingController.