Four behavior-preserving DRY refactors on the autotrade-foundation branch. Files touched: commands.rs, execute.rs, reconcile.rs, positions.rs, runner.rs, repo.rs (all under pmv2/autotrade/). Gate result: EXIT 0, 333 tests pass, clippy clean, no behavior changed.
1. collecting_notify helper (commands.rs)
Both run_reconcile and run_sell previously built an identical Arc<Mutex<Vec<String>>> + closure inline. Extracted into:
fn collecting_notify() -> (Arc<Mutex<Vec<String>>>, impl Fn(String) + Send + Sync)Each call site keeps its own drain policy:
run_reconcile— drains up to 20 +"(+N more)"suffixrun_sell— drains all viaArc::try_unwrap
2. AutotradeDeps::market() + build_autotrade_deps reuse in run_sell (runner.rs, positions.rs, commands.rs)
Three verbatim ClobMarketData { clob: &..., gamma: &..., pool } struct literals were spread across runner.rs::run_exits, positions.rs::run_autotrade_entry, and commands.rs::run_sell. Consolidated by:
- Adding a method on
AutotradeDeps:pub fn market<'a>(&'a self, pool: &'a PgPool) -> ClobMarketData<'a> - Promoting
build_autotrade_depsfrom private topub run_sellnow callsbuild_autotrade_depsinstead of buildingclob + gamma + signerseparately
All three call sites become deps.market(pool).
3. FillOutcome enum + record_fill_then_open fn (execute.rs)
record_placed_fill (in execute.rs) and heal_filled (in reconcile.rs) shared an identical DB sequence: update a fill row, then conditionally reopen. Extracted into a shared helper:
pub enum FillOutcome {
Reopened,
RecordedNotReopened,
Noop,
DbError(String),
}
pub async fn record_fill_then_open(
pool, id, clob_order_id, filled_size, avg_fill_price,
fee_usd, cost_basis, status,
) -> FillOutcomeEach call site keeps its own notify strings and return types — only the DB sequence is shared.
4. Dead repo functions removed (repo.rs)
Two functions carrying #[allow(dead_code)] with zero non-test callers were deleted:
| Function | Why removed |
|---|---|
repo::mark_dry_run | Dry path INSERTs status='dry_run' directly in try_reserve; this wrapper was never called |
repo::load_autotrade_order | Superseded by autotrade_sellable_by_id + load_reconcilable_entries; no callers |
AutotradeOrderRow struct was retained — still used by load_reconcilable_entries.
For Agents
These are purely structural refactors. No API surface changed, no behavior changed, no new migrations. The
FillOutcomeenum andrecord_fill_then_openhelper live inexecute.rsand are the correct seam for any future fill-recording logic.
Quick Reference
- Shared notify factory:
collecting_notify()incommands.rs- Market data shorthand:
deps.market(pool)— needsAutotradeDepsin scope- Fill+reopen seam:
execute::record_fill_then_open— returnsFillOutcome- Dead code removed:
repo::mark_dry_run,repo::load_autotrade_order