T5 of the autotrade build adds the engine_config() converter and the PgPersistence<'a> struct — the two bridge points between the Telegram-controlled AutotradeConfig and the internal engine trait layer.
Files Changed
crates/polymarket-fetch/src/pmv2/autotrade/orders.rs—OrderMode::as_str,OrderMode::from_db,OrderStatus::from_db+ 2 new testscrates/polymarket-fetch/src/pmv2/autotrade/persistence.rs— NEW:engine_config()function +PgPersistence<'a>implementingPersistencecrates/polymarket-fetch/src/pmv2/autotrade/mod.rs—pub mod persistence;added
engine_config: BigDecimal/i32 → EngineConfig
engine_config(cfg: &AutotradeConfig) -> EngineConfig converts the database-typed AutotradeConfig (which uses BigDecimal for NUMERIC columns and i32 for INTEGER columns) into the internal EngineConfig (which uses f64/i64/usize).
Key details:
- Builds
CopyConfigfrom autotrade’s OWN sizing fields (base_size_usd,min_size_usd,max_size_usd,score_threshold,per_event_cap) — NOT from any paper-copy config. category_filteris built by callingparse_topbets_filteron thefiltertext column, splitting on whitespace.BigDecimal::from_f64(x)requiresuse bigdecimal::FromPrimitive;in scope.bd.to_f64()requiresuse bigdecimal::ToPrimitive;. Both traits must be imported together inpersistence.rs.
PgPersistence: mode-aware reservation
PgPersistence<'a> holds &'a PgPool and implements the Persistence trait. All methods are async and operate within a caller-supplied transaction.
Dry-run vs live reservation
try_reserve is mode-aware:
- Dry-run: inserts with terminal status
dry_run— excluded from the dedup partial index, excluded fromexposure_rowsqueries, never counted toward exposure caps or daily spend. These rows are permanently inert. - Live: inserts with status
pending— normal in-flight reservation that the fill/cancel/fail path transitions.
Dedup mechanism
ON CONFLICT (dedup_key)
WHERE status NOT IN ('dry_run','closed','canceled','failed')
DO NOTHING
RETURNING idReturns None when no row was inserted (conflict → dedup skip). The partial index predicate must match exactly: dry_run is excluded from the partial index, so dry-run rows never block future signals on the same dedup_key.
Partial index alignment
The
WHERE status NOT IN (...)clause in theON CONFLICTmust match the partial index definition exactly. If the index excludes a status, that status must appear in theNOT INlist or conflicts will be silently missed/double-inserted.
FOR UPDATE lock on config row
try_reserve acquires a row lock on pmv2_autotrade_config WHERE id = 1 before the INSERT, preventing double-reservation races when the scheduler and bot commands overlap.
Lock order is config-before-order — this is the global lock order documented in pmv2-autotrade-engine-design to prevent ABBA deadlocks.
spend_today_usd
Counts only mode='live' rows from today UTC:
date_trunc('day', now() AT TIME ZONE 'UTC')Dry-run rows never count toward the daily cap.
OrderMode and OrderStatus mappers
Two small but load-bearing pairs added to orders.rs:
| Method | Direction | Note |
|---|---|---|
OrderMode::as_str() | Rust → DB column | Used when inserting/updating |
OrderMode::from_db(s: &str) | DB column → Rust | Used when reading rows |
OrderStatus::from_db(s: &str) | DB column → Rust | Used when reading rows |
from_db methods return Result<Self> and error on unknown strings, keeping deserialization explicit and failing loudly rather than silently defaulting.
Transaction executor pattern
&mut *tx dereference syntax works for sqlx 0.7+ — the same pattern used in crates/polymarket-fetch/src/repo.rs (line 392). Methods accept &mut PgTransaction<'_> and pass &mut *tx to sqlx query executors.
Test counts after T5
- polymarket-fetch main binary tests: 249 passed
- polymarket-data tests: 87 passed
- Gate exit: 0 (PASS)
BigDecimal import pattern (reference)
Both import traits must appear together wherever conversions cross the f64 ↔ BigDecimal boundary:
use bigdecimal::FromPrimitive;
use bigdecimal::ToPrimitive;FromPrimitive provides BigDecimal::from_f64(x). ToPrimitive provides bd.to_f64(). Missing either silently removes the method from scope and produces a confusing “method not found” error.
TDD sequence
Tests were written first (verified failing), implementation added, tests verified passing, then cargo fmt + gate run. This is the standard T5 sequence matching prior autotrade tasks.