Skip to content

Strategy Migration Guide

Use this when refactoring an ib_insync or ib_async strategy to the AQC SDK.

The goal is not just API translation. The goal is to preserve the original strategy's trading and operational behavior.

Core rule

Do not call a migration complete just because the script runs without exceptions.

For trading parity, verify:

  • same expiry universe
  • same exact contracts
  • same pricing inputs
  • same order lifecycle
  • same restart and recovery behavior

Best practices

  • Preserve the original decision path before cleaning up style.
  • Keep the same config values first:
    • DTE windows
    • strike widths
    • thresholds
    • walk logic
    • timeouts
    • order budget
  • For options, qualify on exact expiry, strike, and right.
  • Keep SMART on IBKR option discovery and combo legs unless the original strategy explicitly used something else.
  • Treat the execution engine as part of runtime behavior, not just a transport layer.
  • Reconcile live broker state on restart. Missing local state does not mean flat.
  • For combo strategies, verify entry and exit leg directions, ratios, and exchanges exactly.
  • Verify post-fill behavior explicitly:
    • GTC placement
    • stop placement
    • pending-close reconciliation
    • DTE-stop handling

Common mistakes

  • Replacing exact expiries with approximate dates or month placeholders.
  • Using endpoints that return 200 but the wrong contract universe.
  • Omitting SMART in option secdef discovery or combo-leg exchange fields.
  • Porting only entry logic and forgetting exit, GTC, stop, or reconciliation logic.
  • Assuming local positions_file or risk_file is the source of truth.
  • Treating endpoint health as strategy parity.
  • Changing economic logic during the migration:
    • capital basis
    • carry treatment
    • P&L math
    • daily loss accounting
  • Re-qualifying the same option contracts repeatedly inside one cycle when the original logic reused them.

Practical translation patterns

Quote

python
# ib_insync / ib_async
contract = Stock("QQQ", "SMART", "USD")
ib.qualifyContracts(contract)

# SDK
quote = await client.get_quote("QQQ")
price = float(quote.get("last") or quote.get("mid") or quote.get("close") or 0)

Historical bars

python
# ib_insync / ib_async
bars = ib.reqHistoricalData(contract, endDateTime="", durationStr="2 M", barSizeSetting="1 day")

# SDK
bars = await client.get_historical_bars("QQQ", duration="2 M", bar_size="1 day")

Positions and open orders

python
# ib_insync / ib_async
positions = ib.positions()
orders = ib.openTrades()

# SDK
positions = await client.get_positions()
orders = await client.get_open_trades()

Contract order placement

python
# ib_insync / ib_async
trade = ib.placeOrder(contract, order)

# SDK
result = await client.place_contract_order(
    contract,
    side="BUY",
    quantity=10,
    order_type="MKT",
)

Required parity checks

Before trusting a migrated strategy live, verify:

  • Expiry parity:
    • same DTE-filtered expiry set as the original
  • Contract parity:
    • same expiry, strike, right, and conid target
  • Pricing parity:
    • same quote and greeks inputs for selected legs
  • Execution parity:
    • same entry order shape
    • same walk and cancel behavior
    • same GTC or stop placement sequence
  • State parity:
    • same behavior after restart with live positions or open orders