---
wave: 1
depends_on: []
autonomous: true
files_modified:
  - crates/opendeviationbar-providers/src/binance/combined_ws.rs
requirements:
  - COMB-05
---

# Plan 07-01: Overlapping Reconnection

## Goal

Eliminate the Binance 24h forced-disconnect gap by proactively spawning an overlapping connection at the 23h mark. The new connection receives trades on the same per-symbol channels; once it delivers its first trade, the old connection is cancelled. Processor-level trade-ID monotonicity handles any duplicate trades during the overlap window.

---

## Task 1: Add proactive reconnection timer in `run_with_reconnect()`

<read_first>

- `crates/opendeviationbar-providers/src/binance/combined_ws.rs` (full file)
- `crates/opendeviationbar-providers/src/binance/websocket.rs` (ReconnectionPolicy struct)
  </read_first>

<action>
Modify `run_with_reconnect()` in `combined_ws.rs`:

1. Add constant `const PROACTIVE_RECONNECT_SECS: u64 = 23 * 3600;`
2. Track connection start time with `Instant::now()` (already exists as `connection_start`)
3. Replace the direct `connect_and_demux().await` call with a `tokio::select!` between:
   - The current `connect_and_demux()` task (existing connection)
   - A `tokio::time::sleep(PROACTIVE_RECONNECT_SECS)` timer
4. When the timer fires:
   - Create a `CancellationToken` child for the old connection
   - Spawn a new `connect_and_demux()` as a `tokio::spawn` task using the same senders
   - Use a `tokio::sync::oneshot` channel: the new connection signals after receiving its first trade
   - Wait for the oneshot signal (with a timeout), then cancel the old connection's token
   - Continue the reconnection loop (the new spawned task becomes the "current" connection)
5. When `connect_and_demux()` completes normally (before timer): handle errors with existing Antaeus backoff logic unchanged

This requires modifying `connect_and_demux()` to accept an optional `oneshot::Sender` for first-trade signaling, or wrapping it in a helper that does the signaling externally by monitoring senders.

Simpler approach: wrap the proactive reconnection in the outer loop. When the 23h timer fires, treat it like a controlled disconnect — the existing error handling + backoff reset will reconnect immediately (connection was stable). The key insight: the overlap window is handled by spawning the new `connect_and_demux` before closing the old one, with a brief overlap where both send to the same channels.
</action>

<acceptance_criteria>

- `grep -q "PROACTIVE_RECONNECT_SECS" crates/opendeviationbar-providers/src/binance/combined_ws.rs`
- `grep -q "proactive reconnect" crates/opendeviationbar-providers/src/binance/combined_ws.rs`
- `grep -q "oneshot" crates/opendeviationbar-providers/src/binance/combined_ws.rs`
- `cargo check -p opendeviationbar-providers` passes
  </acceptance_criteria>

---

## Task 2: Add unit test for overlapping reconnection logic

<read_first>

- `crates/opendeviationbar-providers/src/binance/combined_ws.rs` (tests module at bottom)
  </read_first>

<action>
Add a test in the `#[cfg(test)] mod tests` block that verifies:

1. `PROACTIVE_RECONNECT_SECS` equals `23 * 3600` (82800 seconds)
2. The constant is within expected range (>22h, <24h) as a safety check

Note: Full integration testing of the overlapping reconnection (real WebSocket connections, timing) is deferred to Phase 8. Unit tests here verify the configuration and structural correctness.
</action>

<acceptance_criteria>

- `grep -q "test_proactive_reconnect" crates/opendeviationbar-providers/src/binance/combined_ws.rs`
- `cargo nextest run -p opendeviationbar-providers -- test_proactive_reconnect` passes
  </acceptance_criteria>
