Trading Engine

Strategy and Signal System

Core technical engine for condition evaluations, visual strategy compiling, indicators calculation, and orderbook stop-loss adaptation inside DepthSight.

⏱️ 13 min read📊 Level: Intermediate

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.

DatatypePurposeKey Fields
SignalDirectionEnum: LONG, SHORT, NEUTRALUsed for signal direction and trend classification
OrderModeEnum: MARKET, LIMIT_BREAK, LIMIT_RETESTDetermines execution semantics
StrategySignalComplete trade instructiondirection, stop_loss, take_profit, entry_price, mode, partial_targets, risk_pct, risk_usd, no_stop_loss
PartialTargetFractional exit at a specific priceprice, fraction ($0 < f ≤ 1.0$)
DensityInfoOrderbook density clusterprice, size_usd, distance_from_current_price_abs, side
OrderbookAnalysisResultAggregated orderbook statenearest_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.

Sources: Sources:

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 TypeConstantWhat It Evaluates
OrderbookFOUNDATION_ORDERBOOKBid/ask density clusters near current price; support/resistance proximity; approach detection
PatternFOUNDATION_PATTERNClassic candlestick patterns: engulfing, pin bar, doji, inside bar
TrendFOUNDATION_TRENDSMA crossover + RSI direction alignment
LevelFOUNDATION_LEVELSignificant price levels (daily/weekly high/low, local swing points)
Round NumberFOUNDATION_ROUND_NUMBERPsychological price levels based on configurable step definitions
TapeFOUNDATION_TAPE_ACCELERATIONTime-and-sales delta, buy/sell ratio, acceleration metrics
Market ActivityFOUNDATION_MARKET_ACTIVITYTrading session and volume activity filters
Volume ConfirmationFOUNDATION_VOLUME_CONFIRMATIONRelative volume thresholds and NATR filters
Return to LevelFOUNDATION_RETURN_TO_LEVELPrice 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.

Sources: Sources:

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.
Sources: Sources:

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.

Sources:

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 TypeOperators / ModesSource Data
stochastic_conditiongt, lt, cross_above, cross_below%K / %D values
bollinger_bands_conditionprice_below_lower, price_above_upper, width_gt, width_ltBB lower/upper/bandwidth
macd_conditioncrossover, cross_below_signal, hist_gt_zero, hist_lt_zero, value_above, value_belowMACD line / signal / histogram
rsi_conditiongt, lt, gte, lteRSI value vs threshold
ma_cross_conditioncrosses_above, crosses_below, above, belowFast EMA vs slow EMA
trend_directionlong, short, any_trend, flatSMA cross + RSI bounds
value_comparisongt, lt, gte, lte, cross_above, cross_belowDynamic operand pairs (candle, indicator, constant)
tape_conditiongt, lt, gte, lteDelta volume/count, buy/sell ratio, acceleration
time_filterinclude, excludeUTC hour range
natr_filtergt, ltNATR value vs threshold
adx_filtergt, ltADX value vs threshold
volatility_filtergt, lt(High-Low)/Close ratio
trading_sessioninclude, excludeNamed session windows
btc_state_filterState comparisonBTC trend state
price_consolidationRange/ATR ratioSqueeze detection
open_interestgt, ltOI change metrics
correlationThreshold comparisonCross-asset correlation

Scalar vs. Vectorized Contract

Every condition type exposes two evaluation paths:

  1. Scalar functions (evaluate_*_logic): Accept pre-calculated float values, return bool. Used by BaseStrategy in live trading where indicators are already computed per-tick.
  2. Vectorized functions (evaluate_*_vectorized): Accept main_df and signals_df DataFrames, return pd.Series[bool]. Used by FastVectorBacktester for 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 _logic function and the _vectorized function, then register the type in CONDITION_TYPE_ALIASES. The FastVectorBacktester exclusively calls vectorized evaluators, while BaseStrategy.check_signal calls scalar logic — a mismatch between these two paths is the most common source of backtest-vs-live parity bugs.

Sources: Sources: Sources:

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 traceability
  • node_type: The condition type (mapped through normalize_condition_type)
  • params: Condition-specific parameters
  • analysis_level: Either "minute_bar_filter" or "second_bar_trigger", controlling when the condition is evaluated in the controller's two-phase signal detection
  • children: 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.

Sources:

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.

Rendering diagram...

The pipeline executes in strict order:

  1. Data validation: Requires at least 60 bars of 1m kline data for Oracle volatility calculations.
  2. 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.
  3. Compass feature extraction: Eight features including pressure_buy, pressure_sell, absorption, path_resistance, obi_1p, delta_wall_divergence, scalper_natr, and relative_volume.
  4. XGBoost prediction: Supports both multiclass output (3-class: SHORT/SKIP/LONG) and binary output, with configurable min_entry_probability threshold.
  5. 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:

SensorWhat It MeasuresComputation
Memory (Forgetting Speed)Short-term vs long-term volatility ratiovol_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 priceEMA-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.

Sources: Sources:

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: MARKET mode uses trigger_price as the comparison reference; LIMIT modes (LIMIT_BREAK, LIMIT_RETEST) require a positive entry_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_profit is effectively superseded (a warning is logged); if they sum to less than 1.0, a take_profit must 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:

  1. Identify the nearest density cluster on the protective side (bid density for LONG SL, ask density for SHORT SL).
  2. Verify the density is sufficiently far from entry (ORDERBOOK_ADAPT_MIN_DENSITY_DISTANCE_ATR).
  3. Place the adapted SL ORDERBOOK_ADAPT_SL_TICKS_BEHIND_DENSITY ticks behind the density wall.
  4. Reject if the adapted SL would widen the original SL by more than ORDERBOOK_ADAPT_MAX_OFFSET_ATR $\times$ ATR.
  5. 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.

Sources: Sources:

Architectural Summary

The Strategy and Signal System operates on three principles that make it maintainable and extensible:

  1. Separation of evaluation from aggregation: condition_core.py knows nothing about foundations or signal assembly — it only evaluates individual conditions. strategy.py knows 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.
  2. 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.
  3. 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.