# of_signals
`of_signals` contains strategy modules that transform analytics snapshots into stable directional state.
It is intentionally separated from ingestion/runtime plumbing so strategy logic remains easy to test and evolve.
## Core API
- Trait: [`SignalModule`]
- Gate result: [`SignalGateDecision`]
- Built-in modules:
- [`DeltaMomentumSignal`]
- [`VolumeImbalanceSignal`]
- [`CumulativeDeltaSignal`]
- [`AbsorptionSignal`]
- [`ExhaustionSignal`]
- [`SweepDetectionSignal`]
- [`CompositeSignal`]
## New In 0.3.0
`0.3.0` adds runtime-level replay parity coverage that verifies persisted
streams replay into matching signal state. The `SignalModule` trait and built-in
signal constructors remain unchanged.
## New In 0.2.0
Relative to the `0.1.x` line, `of_signals` now includes a broader built-in
catalog:
- [`VolumeImbalanceSignal`]
- [`CumulativeDeltaSignal`]
- [`AbsorptionSignal`]
- [`ExhaustionSignal`]
- [`SweepDetectionSignal`]
- [`CompositeSignal`]
The original trait contract stayed stable, so downstream custom modules do not
need a migration.
## Public API Inventory
Public types:
- [`SignalGateDecision`]
- [`SignalModule`]
- [`DeltaMomentumSignal`]
- [`VolumeImbalanceSignal`]
- [`CumulativeDeltaSignal`]
- [`AbsorptionSignal`]
- [`ExhaustionSignal`]
- [`SweepDetectionSignal`]
- [`CompositeSignal`]
Public constructors:
- [`DeltaMomentumSignal::new`]
- [`VolumeImbalanceSignal::new`]
- [`CumulativeDeltaSignal::new`]
- [`AbsorptionSignal::new`]
- [`ExhaustionSignal::new`]
- [`SweepDetectionSignal::new`]
- [`CompositeSignal::new`]
[`SignalModule`] trait methods:
- `on_analytics(&AnalyticsSnapshot)`
- `snapshot() -> SignalSnapshot`
- `quality_gate(DataQualityFlags) -> SignalGateDecision`
Signal output uses `of_core::SignalSnapshot` and states such as `LongBias`, `ShortBias`, `Neutral`, and `Blocked`.
## SignalModule Contract
[`SignalModule`] is the extension point for strategy logic.
- `on_analytics(&AnalyticsSnapshot)` consumes the latest analytics state and updates internal signal state.
- `snapshot()` returns the last computed [`SignalSnapshot`](of_core::SignalSnapshot).
- `quality_gate(DataQualityFlags)` tells the runtime whether the signal should be blocked under the current feed-quality conditions.
Recommended implementation rules:
- keep updates deterministic so replay and live runs match
- include human-readable `reason` text in the snapshot when practical
- use `confidence` consistently so downstream hosts can compare modules
- block aggressively on stale, gap, or degraded feed conditions when a strategy should not trade through uncertainty
## Constructor Parameter Reference
- [`DeltaMomentumSignal::new`] takes an absolute `delta` threshold.
- [`VolumeImbalanceSignal::new`] takes an absolute session `buy_volume - sell_volume` threshold.
- [`CumulativeDeltaSignal::new`] takes an absolute `cumulative_delta` threshold.
- [`AbsorptionSignal::new`] takes:
- `threshold`: directional pressure required before checking for absorption
- `price_band`: max distance from POC/value location used by the heuristic
- [`ExhaustionSignal::new`] takes an absolute delta threshold for stalled reversal detection.
- [`SweepDetectionSignal::new`] takes:
- `threshold`: directional delta threshold
- `breakout_ticks`: minimum break outside value area
- [`CompositeSignal::new`] takes owned child modules and aggregates their votes.
## Delta Momentum Strategy
[`DeltaMomentumSignal`] is a reference implementation that:
- emits `LongBias` when `delta >= threshold`
- emits `ShortBias` when `delta <= -threshold`
- emits `Neutral` otherwise
- emits `Blocked` in runtime when quality gate fails
## Volume Imbalance Strategy
[`VolumeImbalanceSignal`] is a reference implementation that:
- compares session `buy_volume - sell_volume` against an absolute threshold
- emits `LongBias` when buy pressure dominates
- emits `ShortBias` when sell pressure dominates
- remains `Neutral` while the session imbalance stays inside the configured band
## Cumulative Delta Strategy
[`CumulativeDeltaSignal`] is a session-bias module that:
- compares `cumulative_delta` against an absolute threshold
- emits `LongBias` when session delta remains strongly positive
- emits `ShortBias` when session delta remains strongly negative
- remains `Neutral` while cumulative delta stays inside the configured band
## Absorption Strategy
[`AbsorptionSignal`] is a heuristic module that:
- looks for strong directional delta that fails to move price away from POC
- emits `LongBias` on sell absorption near POC
- emits `ShortBias` on buy absorption near POC
## Exhaustion Strategy
[`ExhaustionSignal`] is a heuristic reversal module that:
- looks for strong directional delta that stalls back near POC
- emits `ShortBias` when buying appears exhausted
- emits `LongBias` when selling appears exhausted
## Sweep Detection Strategy
[`SweepDetectionSignal`] is a breakout module that:
- looks for strong delta alongside a break outside value area
- emits `LongBias` on upside sweeps
- emits `ShortBias` on downside sweeps
## Composite Strategy
[`CompositeSignal`] combines multiple child modules and:
- updates each child on the same analytics snapshot
- emits the majority directional view when one side has more votes
- remains `Neutral` when there is no directional majority
## Output Interpretation
All built-in modules return [`SignalSnapshot`](of_core::SignalSnapshot), which downstream runtimes and bindings expose unchanged.
- `state` is the durable directional state
- `confidence` is a normalized score chosen by the module
- `reason` is short human-readable rationale
- `quality_flags` echoes the quality context that contributed to blocking or caution
Built-in modules are intentionally heuristic rather than venue-specific alpha models.
They are meant as production-ready references and defaults, not as the only strategy approach.
## Quick Example
```rust
use of_core::{AnalyticsSnapshot, SignalState};
use of_signals::{DeltaMomentumSignal, SignalModule};
let mut signal = DeltaMomentumSignal::new(100);
signal.on_analytics(&AnalyticsSnapshot {
delta: 150,
..Default::default()
});
let snapshot = signal.snapshot();
assert!(matches!(snapshot.state, SignalState::LongBias));
```
## Alternative Module Example
```rust
use of_core::{AnalyticsSnapshot, SignalState};
use of_signals::{SignalModule, VolumeImbalanceSignal};
let mut signal = VolumeImbalanceSignal::new(100);
signal.on_analytics(&AnalyticsSnapshot {
buy_volume: 350,
sell_volume: 200,
..Default::default()
});
let snapshot = signal.snapshot();
assert!(matches!(snapshot.state, SignalState::LongBias));
```
## Composite Example
```rust
use of_core::{AnalyticsSnapshot, SignalState};
use of_signals::{
CompositeSignal, CumulativeDeltaSignal, DeltaMomentumSignal, SignalModule,
VolumeImbalanceSignal,
};
let mut signal = CompositeSignal::new(vec![
Box::new(DeltaMomentumSignal::new(100)),
Box::new(VolumeImbalanceSignal::new(100)),
Box::new(CumulativeDeltaSignal::new(150)),
]);
signal.on_analytics(&AnalyticsSnapshot {
delta: 200,
cumulative_delta: 250,
buy_volume: 400,
sell_volume: 100,
..Default::default()
});
let snapshot = signal.snapshot();
assert!(matches!(snapshot.state, SignalState::LongBias));
```
## Quality Gate Example
```rust
use of_core::DataQualityFlags;
use of_signals::{DeltaMomentumSignal, SignalGateDecision, SignalModule};
let signal = DeltaMomentumSignal::default();
let gate = signal.quality_gate(DataQualityFlags::SEQUENCE_GAP);
assert_eq!(gate, SignalGateDecision::Block);
```
## Implementing Your Own Signal Module
Implement [`SignalModule`] and keep it:
- deterministic (important for replay parity)
- explicit about confidence and reason fields
- strict about quality gating for unsafe feed states
- compatible with the stable `SignalSnapshot` contract so bindings and FFI callers keep working
## Real-World Use Cases
### 1. Gate entries on feed quality
Even a simple threshold signal becomes materially safer when `quality_gate(...)`
blocks action during stale, gap, or degraded feed states.
### 2. Build multi-factor orderflow strategies
Use several modules together to express:
- context: `CumulativeDeltaSignal`
- trigger: `SweepDetectionSignal`
- reversal filter: `AbsorptionSignal` or `ExhaustionSignal`
### 3. Keep strategy logic deterministic in replay
Because modules consume normalized analytics instead of live provider payloads,
the same module can be replayed over historical sessions and compared to live
behavior with much less drift.
## Strategy Pattern: Composite Confirmation
A practical strategy stack often looks like:
1. `CumulativeDeltaSignal` for directional regime bias
2. `VolumeImbalanceSignal` for immediate orderflow pressure
3. `SweepDetectionSignal` for breakout confirmation
4. `CompositeSignal` to require majority confirmation
## Detailed Example: Build A Composite Intraday Bias Module
```rust
use of_core::{AnalyticsSnapshot, DataQualityFlags, SignalState};
use of_signals::{
CompositeSignal, CumulativeDeltaSignal, SweepDetectionSignal, SignalGateDecision,
SignalModule, VolumeImbalanceSignal,
};
fn main() {
let mut signal = CompositeSignal::new(vec![
Box::new(CumulativeDeltaSignal::new(400)),
Box::new(VolumeImbalanceSignal::new(150)),
Box::new(SweepDetectionSignal::new(200, 4)),
]);
let analytics = AnalyticsSnapshot {
buy_volume: 900,
sell_volume: 500,
delta: 400,
cumulative_delta: 650,
last_price: 505_075,
point_of_control: 505_000,
value_area_low: 504_750,
value_area_high: 505_050,
..Default::default()
};
if signal.quality_gate(DataQualityFlags::NONE) == SignalGateDecision::Pass {
signal.on_analytics(&analytics);
let snapshot = signal.snapshot();
if matches!(snapshot.state, SignalState::LongBias) {
println!("long bias: {}", snapshot.reason);
}
}
}
```
## Detailed Example: Write Your Own Signal Module
```rust
use of_core::{AnalyticsSnapshot, DataQualityFlags, SignalSnapshot, SignalState};
use of_signals::{SignalGateDecision, SignalModule};
struct POCReclaimSignal {
last: SignalSnapshot,
}
impl Default for POCReclaimSignal {
fn default() -> Self {
Self {
last: SignalSnapshot {
module_id: "poc_reclaim_v1",
state: SignalState::Neutral,
confidence_bps: 0,
quality_flags: 0,
reason: "init".to_string(),
},
}
}
}
impl SignalModule for POCReclaimSignal {
fn on_analytics(&mut self, analytics: &AnalyticsSnapshot) {
let state = if analytics.delta > 200 && analytics.point_of_control >= analytics.value_area_low {
SignalState::LongBias
} else if analytics.delta < -200 && analytics.point_of_control <= analytics.value_area_high {
SignalState::ShortBias
} else {
SignalState::Neutral
};
self.last = SignalSnapshot {
module_id: "poc_reclaim_v1",
state,
confidence_bps: 7000,
reason: "POC reclaim heuristic".to_string(),
quality_flags: 0,
};
}
fn snapshot(&self) -> SignalSnapshot {
self.last.clone()
}
fn quality_gate(&self, flags: DataQualityFlags) -> SignalGateDecision {
if flags.intersects(
DataQualityFlags::STALE_FEED
| DataQualityFlags::SEQUENCE_GAP
| DataQualityFlags::ADAPTER_DEGRADED,
) {
SignalGateDecision::Block
} else {
SignalGateDecision::Pass
}
}
}
```