Skip to content

refactor(historical): use Subscription<HistoricalBarUpdate> (closes #431)#488

Merged
wboayue merged 3 commits intomainfrom
refactor/historical-streaming-subscription-431
May 2, 2026
Merged

refactor(historical): use Subscription<HistoricalBarUpdate> (closes #431)#488
wboayue merged 3 commits intomainfrom
refactor/historical-streaming-subscription-431

Conversation

@wboayue
Copy link
Copy Markdown
Owner

@wboayue wboayue commented May 2, 2026

Summary

  • Replace HistoricalDataStreamingSubscription with Subscription<HistoricalBarUpdate> by adding a StreamDecoder<HistoricalBarUpdate> impl that mirrors the MarketDepths / Bar / Trade pattern in src/market_data/realtime/.
  • Factory functions in both historical/sync.rs and historical/async.rs collapse to encode + builder.send::<HistoricalBarUpdate>(). Custom struct, Drop, and inline message dispatch deleted in both files.
  • Net -271 lines (132 insertions, 403 deletions).

Closes #431.

Why

The custom subscription duplicated infrastructure that the generic Subscription<T> already provides: AtomicBool cancelled guard, message_bus storage, cancel encoding, tokio::spawn in async Drop, error storage, timezone-fallback resolution, and inline message-type dispatch. Each lived in two places (sync + async).

After the refactor each piece has one job:

  • HistoricalBarUpdate::decode — message-type dispatch.
  • per-message decode_historical_data{,_update,_end} — payload parsing (already split, reused).
  • HistoricalBarUpdate::cancel_message — cancel encoding.
  • Subscription<T> — lifecycle (cancel, drop dedup, error storage).
  • factory — request encoding + send.

HistoricalBarUpdate joins the same plug-in family as Bar, Trade, BidAsk, MarketDepths — all consumed by the same generic Subscription<T>.

Breaking changes (intentional, v3)

  • Async: subscription.next().await now returns Option<Result<HistoricalBarUpdate, Error>>. The separate error() accessor is gone — errors arrive via next().
  • Sync: unchanged in this PR (still Option<HistoricalBarUpdate> + error()). Aligning sync with async is tracked in Align sync Subscription<T>::next() with async: return Option<Result<T, Error>> #487.
  • HistoricalDataStreamingSubscription is no longer exported. Callers use Subscription<HistoricalBarUpdate> directly. Per the v3 philosophy in CLAUDE.md, no type alias.

Test plan

  • cargo fmt
  • cargo clippy --all-targets -- -D warnings
  • cargo clippy --all-targets --features sync -- -D warnings
  • cargo clippy --all-features
  • cargo test (default/async): 880 lib + 73 doc + integration — all green
  • cargo test --no-default-features --features sync: 884 + 124 — all green
  • cargo test --all-features: 1064 + 148 — all green
  • cargo build --example async_historical_data — clean
  • Manual against IB Gateway paper trading: verify Historical → End → ongoing Updates with keep_up_to_date=true, and clean Ctrl-C cancel

Behavioral assertions verified by tests

  • Drop with no prior cancel → exactly one CancelHistoricalData proto sent (factory-driven test).
  • Explicit cancel().await then drop → exactly one cancel sent (dedup via Subscription's AtomicBool).
  • Error response → next() yields Some(Err(_)) (async) / None + error() populated (sync).

…h generic Subscription

Custom struct duplicated cancel-on-drop, AtomicBool dedup, message_bus
storage, tokio::spawn in async Drop, and inline message dispatch — all
already provided by the generic Subscription<T>. Replaced with a 30-line
StreamDecoder impl (mirrors MarketDepths / Bar / Trade pattern) and a
3-line factory body. Net -271 lines.

Async API breaks: next() now returns Option<Result<HistoricalBarUpdate,
Error>> and there is no separate error() accessor. Sync API unchanged
in this PR; see #487 for the follow-up.

Drop tests rewired to drive through the public factory rather than the
deleted internal constructor, so they exercise the real cancel-on-drop
path end-to-end.

Closes #431.
@wboayue wboayue merged commit dfbfeac into main May 2, 2026
3 checks passed
@wboayue wboayue deleted the refactor/historical-streaming-subscription-431 branch May 2, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor HistoricalDataStreamingSubscription to use generic Subscription<T>

1 participant