Canonical signal type that all autotrade sources emit to trigger trades. Implemented in crates/polymarket-fetch/src/pmv2/autotrade/signal.rs on branch autotrade-foundation.
Superseded shape (2026-06-17)
The
SignalAction::{Enter{reference_price},Exit}discriminator documented below was replaced by anExecutionenum (Directive{max_price}for weather vsContext{reference_price,conviction,source_score}for cohort) whenTradeSignalwas generalized for multi-source NATS ingestion.dedup_key()now returnssignal_iddirectly. The cohortsignal_idis set equal to the legacydedup_key(..,"enter",..)so dedup stays byte-identical mid-migration. See pmv2-position-manager-nats-consumer for the current shape.
Location
- File:
crates/polymarket-fetch/src/pmv2/autotrade/signal.rs - Module declared in:
crates/polymarket-fetch/src/pmv2/autotrade/mod.rsaspub mod signal;
Public Surface
SignalAction enum
| Variant | Fields | Purpose |
|---|---|---|
Enter | reference_price: f64 | Request to open a position; reference_price is frozen at first detection and feeds the chase guard in the engine |
Exit | — | Request to close an existing position |
SignalAction::label() is a const fn returning "enter" or "exit". Clippy use_self lint: match arms use Self::Enter/Self::Exit (not SignalAction::).
TradeSignal struct
| Field | Type | Purpose |
|---|---|---|
source_id | &'static str | Identifies which signal source emitted this (e.g. "first_mover") |
action | SignalAction | Enter or Exit |
condition_id | String | CTF condition ID for the market |
outcome_index | u8 | Outcome side (YES=0, NO=1 typically) |
outcome_label | String | Human-readable outcome label |
event_slug | String | Gamma event slug |
slug | String | Market slug |
origin_ref | String | Opaque ref to the event/row that triggered this signal (for tracing) |
conviction | f64 | bet_confidence value from the scoring layer; feeds position sizing |
source_score | Option<f64> | Emitter’s trader score, if available |
tier | Option<Tier> | Tier classification from pmv2_traders; Tier::Core is the first launch cohort |
instance | String | Unique instance ID for dedup/idempotency |
Tier re-export
pub use crate::pmv2::model::Tier; — Tier was already pub in the model with variants Core, Proven, Emerging. No model changes were needed; the re-export makes it convenient to import from signal.rs.
TradeSignal::dedup_key()
Delegates entirely to crate::pmv2::autotrade::orders::dedup_key() using all 6 discriminators. This keeps the single dedup definition in orders.rs as the canonical reference; signal.rs calls it rather than re-implementing it.
Tests
2 unit tests, both passing:
dedup_key_uses_all_discriminators— builds aTradeSignal, mutates each of the 6 discriminators in turn, asserts each mutation changes the dedup keyexit_action_key_differs_from_enter— same signal withEntervsExitproduces a different key (action is one of the 6 discriminators)
Clippy Compliance
Passes scripts/gate.sh clean (clippy pedantic+nursery with -D warnings). Key fix applied:
Clippy fix applied
fn label()promoted toconst fn label()to satisfy themissing_const_for_fnlint. Match arms useSelf::Enter/Self::Exitto satisfy theuse_selflint — the function was originally written withSignalAction::, which clippy rejects.
Design Notes
For Agents
TradeSignalis the single pluggable seam between any signal source and the engine. The engine’singest(&TradeSignal)method is source-agnostic — the same sizing/risk/execution/reconcile machinery handles every source. Adding a new source = implement an emitter that populates and hands over aTradeSignal. Theinstancefield carries the idempotency key all the way to order placement.
reference_pricesemanticsThe
Entervariant carriesreference_price, which is frozen at the moment of first detection by the signal source. The engine uses this to gate the marketable-limit FAK chase — if the current ask drifts too far abovereference_price, the order is skipped (chase guard). This is important: sources must capture the price they observed, not defer to the engine.
dedup_keyownershipThe canonical dedup function lives in
orders.rs, notsignal.rs. If the discriminator set ever changes, change it inorders.rs—TradeSignal::dedup_key()will automatically reflect it since it just delegates.
Relationship to Autotrade Plan
This is P2.1 — the canonical signal type. The task sequence on autotrade-foundation:
| Task | Description |
|---|---|
| Task 1 | Vendor + pin rs-clob-client-v2 into crates/polymarket-clob/ |
| Task 2 | authz.rs — owner-only command authorization (see autotrade-authz-module) |
| P2.1 | signal.rs — TradeSignal + SignalAction types (this note) |
| Next | engine::ingest(&TradeSignal) implementation; first-mover source emitter |
Related
- pmv2-autotrade-engine-design — full engine design (locked);
TradeSignalis theingestseam - autotrade-authz-module — Task 2 (authz); same branch
- first-mover-overlap-watcher — first signal source the engine will consume
- pmv2-two-tier-trader-scoring —
Tierandconviction/bet_confidenceoriginate here - polymarket-fetch — project overview