Back to Documentation

Changelog

Release notes for the PaperBroker Python Client

Preview track (v0.2.5 → v0.2.9): v0.2.5 through v0.2.9 are tracking releases leading to v0.3.0. Production users should pin v0.2.4 or wait for v0.3.0. v0.2.7 is the third of five tracking releases.
v0.2.7 Latest Preview
May 25, 2026

Alpha framework refactor. Multi-instrument-first SignalDrivenAlpha with explicit Signal / OrderRequest split, AlphaContext object, joint trigger across all subscribed bars, and exit semantics via CloseSignal / TakeProfitSignal / StopLossSignal subtypes. Breaking vs v0.2.6 (no shim) plus one client-surface default change: place_order default TIF is now "DAY" (was "GTC") — Vietnam venues only accept DAY.

Added
  • Multi-instrument-first SignalDrivenAlphainstruments: list[str], per-symbol qty, joint trigger across all subscribed bars
  • Signal / OrderRequest value objects — one signal can fan out to N orders or trade a different symbol (cross-asset)
  • AlphaContextbars / quotes / positions / open_orders / signals / account / now / triggered_by per trigger; signature stable for future additions
  • plan_orders hook — default 1:1, override for iceberg, cross-asset, multi-leg execution
  • CloseSignal / TakeProfitSignal / StopLossSignal — framework auto-sizes to abs(position) and routes MARKET
  • OrderState / SignalState rich lifecycle tracking; framework subscribes to all 5 order-lifecycle FIX events
  • trigger_on_quote = True + should_evaluate_on_quote() — quote-driven alphas (market making, scalping)
  • Fail-loud validation — AlphaConfig.__post_init__, from_paper() env-strict, declared_params typo guard
  • Auto-start MD backend — alpha.start() calls market_data.start() (Kafka requires; Redis no-op)
  • Examples consolidated 18 → 14 (+ 15_alpha_mm_basic.py); covers single-instrument, pair-spread, cross-asset, iceberg, exit patterns, market making
Breaking
  • place_order(tif=...) default "GTC""DAY". HSX/HNX/HNXDS reject GTC as GTC_UNSUPPORTED_DAY_ONLY.
  • Unknown tif= string raises ValueError (was silently routed to IOC). "FOK" now correctly mapped to TimeInForce_FILL_OR_KILL.
  • VNSignalDrivenAlphaSignalDrivenAlpha; VNTradingSessionTradingSession. No shim.
  • Hook signatures: all take ctx: AlphaContext. get_signal(...) -> strget_signals(...) -> list[Signal].
  • AlphaConfig(instrument="X", ...)AlphaConfig(instruments=["X"], ...).
  • ctx.open_entries: dict[str, list[str]]ctx.open_orders: dict[str, list[OrderState]] (rich state).
  • Framework no longer blocks signals when entries are open — alpha owns concurrency policy. Add an explicit if ctx.open_orders.get(sym): return [] guard for single-entry semantics.
Multi-instrument joint trigger
class SpreadPairAlpha(SignalDrivenAlpha):
    declared_params = {"window", "entry_z", "exit_z"}

    def get_indicators(self, ctx):
        a, b = self.config.instruments
        spreads = [ba.close - bb.close
                   for ba, bb in zip(ctx.bars[a], ctx.bars[b])]
        return {"z": _rolling_z(spreads, self.config.params["window"])}

    def get_signals(self, ind, ctx):
        # ... emit a list of Signals (multi-leg atomic) ...

alpha = SpreadPairAlpha.from_paper(
    instruments=["HNXDS:VN30F2606", "HNXDS:VN30F2609"],
    sub_account="main", timeframe="1m",
    qty={"HNXDS:VN30F2606": 1, "HNXDS:VN30F2609": 1},
    params={"window": 30, "entry_z": 2.0, "exit_z": 0.5},
)
alpha.run()
Gate 1.5 paper pilot — PASS
  • 5-min pair-spread pilot on F2606 + F2609 (Kafka MD): 10 joint triggers, 0 unhandled exceptions, atomic 2-leg dispatch verified.
  • Redis pub/sub observed ~0.1 tick/s front-month, ~0 back-month. Use Kafka for multi-instrument alphas.
v0.2.6 Preview
May 22, 2026

Alpha framework v1. SignalDrivenAlpha base class (single-instrument), 6 execution + risk primitives, M1 client facade closure, and YAML-driven CLI paperbroker run --config X.yaml. Also introduces the cancel_order tuple return.

Added
  • SignalDrivenAlpha sealed-lifecycle base class with 4 abstract hooks — complete RSI alpha in ~40 lines of strategy code
  • Execution primitives — BarAggregator, TradingSession (HCM exchange calendar), StateStore (JSON K/V), PositionTracker
  • Risk plugin — KillSwitch (equity floor; halt or halt_and_flatten)
  • M1 facade closure — wait_for, is_order_done, cleanup_order, get_order_cumqty / leavesqty / text, last_logout_reason
  • CLI — paperbroker run --config X.yaml and paperbroker diagnose (optional extras [cli])
  • 2 new examples — 13_alpha_rsi_1m.py (Gate 1 pilot), 14_alpha_bollinger_bands.py
Breaking
  • cancel_order(cl_ord_id, timeout) -> tuple[bool, str] — waits for server confirmation, returns (is_terminal, status). Most callers ignoring the return value still work; truthiness checks (if not client.cancel_order(...)) break.
  • Legacy fire-and-forget restored via client.cancel_order_v1(cl_ord_id) (DeprecationWarning, removed in v0.3.0).
Tuple-return usage
is_terminal, status = client.cancel_order(cl_ord_id, timeout=5.0)
if is_terminal:
    print(f"Cancel confirmed: {status}")
else:
    print(f"Still pending — status={status} (may need manual retry)")

# Or restore pre-v0.2.6 behavior:
client.cancel_order_v1(cl_ord_id)         # DeprecationWarning
v0.2.5 Preview
May 22, 2026

Pure architecture refactor — zero new public API. Cleans up internal layout so subsequent tracking releases can ship into a clean structure. session/fix/; REST split into account.py / metrics.py / transactions.py; client.py shrunk 679 → 356 lines via facade unification. All v0.2.4 imports still work via deprecation shims.

Added (no new public symbols)
  • PaperBrokerClient(enable_fix=False) — REST-only mode (macOS arm64 / no QuickFIX). Lazy QuickFIX import; enable_fix=None auto-detects.
  • Flat facade attrs — client.orders (OrderManager or None), client.accounting (AccountClient), client.is_fix_enabled.
  • Order routing 4 hops (3 pass-through) → 1 hop. Tests now mock 1 layer instead of 3.
  • New module layout — paperbroker/fix/ (engine + order_manager + order_store), paperbroker/execution/recovery.py (RecoveryManager), paperbroker/alpha/ and paperbroker/risk/ reserved namespaces.
Deprecation shims (removed in v0.3.0)
  • paperbroker.session.*paperbroker.fix.*. Old imports emit DeprecationWarning.
  • paperbroker.rest.account_clientpaperbroker.rest.account.
  • paperbroker.rest.rest_sessionpaperbroker.rest.session.
  • PaperBrokerRESTClient retained as shim over PaperBrokerClient(enable_fix=False).
v0.2.4 Stable
March 17, 2026

Introduces SQLite-based order persistence — solving a critical issue where active order IDs are lost if the program crashes or is terminated unexpectedly. Orders are now saved to SQLite on every lifecycle event and can be recovered on restart. Recommended for production.

Added
  • order_store_path constructor parameter — enables SQLite persistence (default: "orders.db", set None to disable)
  • recover_pending_orders() method — reloads active orders from SQLite and rehydrates in-memory state so cancel_order() works immediately after restart
  • New OrderStore module with WAL journal mode, atomic commits, and thread-safe access
Design
  • Order state persisted at 3 critical moments: place_order(), execution report received, and every status change
  • Fail-safe design — SQLite errors are logged but never crash the trading flow
  • 100% backward compatible — no breaking changes, no new dependencies (sqlite3 is stdlib)
Recovery Usage
client.connect()
client.wait_until_logged_on(timeout=10)

# Recover orders from previous session
pending = client.recover_pending_orders()
if pending:
    for order in pending:
        print(f"  {order['cl_ord_id']}: {order['side']} "
              f"{order['qty']}x {order['symbol']} @ {order['price']}")
        client.cancel_order(order["cl_ord_id"])