Python SDKv0.3.7

HiveQ Flow API Reference

Institutional-grade quantitative trading and backtesting — in a few lines of Python. You author the strategy on your machine; HiveQ runs it on the engine that has executed billions of dollars in live trades — sourcing the data, simulating real market microstructure, and handing back a full performance report.

bash
pip install hiveq-sdk

Overview

HiveQ Flow is a thin client: it captures and deploys your strategy, then the platform runs the engine, fetches the data, and returns results. You write the what (your trading logic); the platform handles the how — data, execution, analytics, and scale. You can't pull market data locally — the engine has it.

Why HiveQ

  • A simulator built to mirror production. Orders clear against real market microstructure — tick-level data, exchange session windows, primary-exchange auctions, per-asset fee models, slippage, and tick-size rounding.
  • Research → live, same code. The strategy you backtest runs unchanged in paper and live trading — promote a validated backtest from the platform in one click.
  • Every run tracked & versioned. Strategy code and config are captured per run, so any backtest is reproducible — inspect the exact code behind a result and build from it.
  • Production execution algos built in. POV, TWAP, and open/close auctions — the same algorithms used in live trading, not approximations.
  • Multi-asset, multi-strategy. Equities, futures (continuous contracts with automatic rollover), and options down to 0DTE — together in one backtest, with TCA and PDF tearsheets out of the box.

The model in one minute

  • One class, callback methods. Implement only the events you care about — on_start, on_bar, on_order, and more.
  • Subscribe in on_start. Register your data there so it's ready before the run begins.
  • Fills arrive in on_order — there is no separate fill callback; check order.is_filled.
  • Time is U.S. Eastern. ctx.now() and every timestamp are already in market time (ET) — no timezone math.
Install hiveq-sdk · import root hiveq.flow · Python >=3.11. (Don't install the separate hiveq-flow engine package alongside it — same namespace.) This reference covers backtest authoring, reading results, and remote deploy. Live trading is out of scope here.

Install & Sign-in

bash
pip install hiveq-sdk

Sign-in is fully automatic. The first time you run a backtest, a browser opens for you to sign in (or sign up); the run waits while you do, then continues — and every later run reuses your access with no prompt. There is no "set up your key first" step, nothing to copy, nothing to configure.

A HiveQ API key is the only credential, and the SDK provisions and manages it for you — never hard-code keys or set them by hand. Identity (user, org) is resolved from the key. The first-run wait while the browser is open is expected — sign in once and you're set.

Quickstart

A complete strategy: subscribe in on_start, react in on_bar, place orders through ctx, then deploy with run_backtest and read the report.

python
import hiveq.flow as hf
from hiveq.flow import StrategyConfig
from hiveq.flow.config import AssetType
from hiveq.flow.logger import logger as _get_logger

logger = _get_logger()   # module-level — REQUIRED in every strategy

class BuyAndHold:
    def __init__(self):
        self.bought = False

    def on_start(self, ctx, event):
        ctx.subscribe_bars(ctx.strategy_config.symbols,
                           asset_type=AssetType.EQUITY, interval='1m')
        logger.info(f"[START] subscribed 1m bars for {ctx.strategy_config.symbols}")

    def on_bar(self, ctx, event):
        bar = event.data()                       # -> SigmaBar
        logger.debug(f"[BAR] {bar.symbol} {bar.time} close={bar.close:.2f} bought={self.bought}")
        if not self.bought and ctx.is_flat(bar.symbol):
            logger.info(f"[ENTRY] buying 100 {bar.symbol} at {bar.close:.2f}")
            ctx.buy_order(bar.symbol, quantity=100)
            self.bought = True

    def on_order(self, ctx, event):              # fills come HERE, not on_order_filled
        order = event.data()                     # -> SigmaOrder
        logger.info(f"[ORDER] {order.symbol} status={order.status} filled={order.is_filled}")
        if order.is_filled:
            fill = order.last_fill               # -> SigmaFill

# run_backtest returns a Run HANDLE, not a report directly.
run = hf.run_backtest(
    strategy_configs=[StrategyConfig(name='BuyAndHold', type='BuyAndHold')],
    symbols=['AAPL'],
    start_date='2025-08-01',
    end_date='2025-08-31',
    # declare the data source; the schema also sets the fill model (bars vs ticks)
    data_configs=[{'type': 'hiveq_historical', 'dataset': 'HIVEQ_US_EQ',
                   'schema': ['bars_1m']}],
)

print(f"run {run.run_id} task {run.task_id}")    # returns immediately (silent=True is the default)
run.wait(progress=False)                         # block quietly until terminal — no live progress bar
report = run.report()                            # -> PerformanceReport (the deliverable)
print(report.return_stats.to_string())
Three non-negotiables: StrategyConfig.type is the class name as a string (must match exactly), and run_backtest(...) returns a Run handle — call run.report() for results. Every strategy must also use the HiveQ logger throughout its callbacks and decision branches.

Strategy Contract

A strategy is a plain Python class with per-event callback methods, each with the signature (self, ctx, event). Define only the handlers you need.

python
class MyStrategy:
    def __init__(self): ...               # per-strategy state
    def on_start(self, ctx, event): ...   # subscribe here
    def on_bar(self, ctx, event): ...     # event.data() -> SigmaBar
    def on_order(self, ctx, event): ...   # fills/rejects/cancels -> SigmaOrder
    def on_position(self, ctx, event):... # event.data() -> SigmaPosition
    def on_timer(self, ctx, event): ...   # event.data() -> TimerEventData
    def on_rollover(self, ctx, event):... # futures contract roll

Recognized callbacks

on_start, on_stop, on_bar, on_trade, on_quote, on_snap, on_order, on_position, on_timer, on_custom_data, on_index_price (alias on_index), on_rollover, on_imbalance, on_executor, on_security_event. Unknown method names are ignored. on_imbalance delivers an ImbalanceData from the auction imbalance feed — it needs the early_imbalance schema in data_configs; on_executor (EXECUTOR_EVENT) and on_security_event (SECURITY_EVENT) carry opaque payloads.

There is no on_order_filled — fills are delivered to on_order. Don't place orders in on_stop; the engine has already stopped and rejects them.

Required strategy instrumentation

Every strategy must create the HiveQ logger at module level and instrument every callback and decision branch. Use logger.debug(...) for per-bar state and condition checks; use logger.info(...) for signals, orders, and fills. The default engine level is WARNING, so healthy runs remain quiet even though the diagnostic context is already in the strategy.

python
from hiveq.flow.logger import logger as _get_logger
logger = _get_logger()   # module level — never logging.getLogger(__name__)

# Required coverage:
# on_start -> subscriptions/config (INFO)
# on_bar -> time, price, state, every condition outcome (DEBUG)
# order placement + on_order -> side, quantity, reason, status/fill (INFO)
Do not use logging.getLogger(__name__) or logging.basicConfig; the executor silences them. Do not leave a hiveq_log_level override in normal or delivered code — enable it only for a diagnostic re-run.

Order lifecycle contract (FIX-style: status ≠ events)

order.status is the order's state; events are the history. Terminal statuses (FILLED / CANCELED / REJECTED) are sticky — later request-level events never change them. The canonical race: you cancel a resting order but a fill lands while the cancel is in flight → you receive ORDER_FILLED first (the fill is never lost), then ORDER_CANCEL_REJECTED ("too late to cancel"). order.status reads FILLED on both.

  • Act on fills from the ORDER_FILLED event, using the cumulative filled_qty/leaves_qty (idempotent).
  • Treat ORDER_CANCEL_REJECTED / ORDER_MODIFY_REJECTED as informational no-ops when order.is_filled — do not count them toward reject/error limits.
  • ORDER_REJECTED is reserved for the order itself being rejected (entry rejects); it never fires for cancel/replace request rejections.
  • An order canceled after a partial fill ends CANCELED with filled_qty > 0.
  • Never infer disposition from the last event — use order.status / filled_qty.
There is no on_order_filled — fills arrive on on_order; check order.is_filled / order.status / order.last_fill.

Context API — ctx

Everything you do at runtime goes through ctx (a SigmaContext): subscribe to data, place & manage orders, query positions, read the clock, and run managed executors.

Subscriptions (call in on_start)

python
ctx.subscribe_bars(symbols, asset_type=AssetType.EQUITY, interval='1m')
ctx.subscribe_trades(symbols)     # tick prints  (asset_type defaults to EQUITY)
ctx.subscribe_quotes(symbols)     # bid/ask      (asset_type defaults to EQUITY)
ctx.subscribe_option_snaps('SPY', expiration_type='0dte')  # '0dte' | 'YYYY-MM-DD' | datetime

# futures: by symbol string, or the root= / contract= / continuous= selectors
ctx.subscribe_futures_bars(symbols=['ES.c.0'], interval='1m')
ctx.subscribe_futures_trades(symbols=['ES.c.0'])

# custom / quant signals — data_id must match the 'id' in data_configs
ctx.subscribe_data(data_id='mysignals')   # -> on_custom_data
There is no ctx.subscribe_futures_quotes — bars and trades have dedicated futures methods, quotes do not. For futures NBBO/tbbo quotes use ctx.subscribe_quotes(['ES.c.0'], asset_type=AssetType.FUTURES).

Order placement

python
ctx.buy_order(symbol, quantity, order_type=OrderType.LIMIT, limit_price=px)
ctx.sell_order(symbol, quantity)         # exit long
ctx.short_order(symbol, quantity)        # open short

# sizing helpers (off the current net position)
ctx.close_position(symbol)               # flatten one symbol (no-op if flat)
ctx.order_to_target(symbol, 100)         # signed target: +long / -short / 0
ctx.flatten_all()                        # close every position in this strategy
Only the three sizing helpers (close_position / order_to_target / flatten_all) are idempotent — they skip if a working order already exists for the symbol, so they're safe to call every bar. buy_order/sell_order/short_order have no such guard — calling them every bar stacks orders. Round your own limit_price/stop_price to the instrument tick with adjust_tick_size(symbol, px).

Order management & queries

python
ctx.cancel_order(order_id)
ctx.modify_order(order_id, limit_price=..., stop_price=...)
ctx.cancel_all_orders(symbol)

ctx.net_position(symbol)   # signed float (+long / -short / 0)
ctx.is_flat(symbol)        # bool
ctx.has_open_order(symbol) # bool
ctx.now()                  # ET datetime   (time is ALWAYS U.S. Eastern)
ctx.set_timer('poll', timedelta(minutes=1))   # fires on_timer

Executors — managed order working (POV / TWAP / VWAP / AUCTION)

An executor is a server-side algo that owns a target's full lifecycle — slicing the parent quantity, repricing, replacing/cancelling, and aggregating fills. Reach for one when execution quality matters (large sliced orders, live order-chasing, auction routing); for a simple one-shot market order, a direct buy_order is simpler.

python
params = ctx.build_executor_params(
    symbol='AAPL', quantity=1000, side='BUY',
    executor_type='POV', participate_pct=10,   # work at 10% of volume
)
handle = ctx.add_executor(params)              # start it (None if it failed)
ctx.executor_state(handle)                     # "PARTIALLY_FILLED" | "FILLED" | ...

# re-target IN PLACE — never add a second executor for the same target:
eid = str(handle.executorID)
ctx.replace_executor_params_by_id(eid, new_params)
Executors need a tick stream, not bars — subscribe with ctx.subscribe_trades(...) (schema eq_trades/fut_trades). Keep one handle per target.

Events & Payloads

Each callback receives an event; call event.data() for the payload. The concrete type depends on event.type:

python
event.type        # EventType  (branch on this)
event.data()      # payload object (type depends on event.type)
event.time        # ET datetime
event.ts_event    # int nanoseconds

Payload types

  • SigmaBarsymbol, open, high, low, close, volume, interval, time
  • SigmaOrderstatus, filled_qty, avg_px, is_filled, last_fill, commission
  • SigmaFilllast_qty, last_px, commission, liquidity_side (via order.last_fill)
  • SigmaPositionquantity, avg_price, realized_pnl, unrealized_pnl, total_pnl
  • SigmaTradeTick / SigmaQuoteTick — tick prints & bid/ask
  • SigmaSnapData — options snapshot (strike, option_type, bid_px, ask_px)
  • SigmaCustomData — custom/CSV rows; read columns with column_data(name) (values are strings — cast yourself; quant signals arrive as a signal_json column)
  • Rollovercontinuous_symbol, prev_contract, current_contract
  • ImbalanceData — auction imbalance record (NYSE/Arca/Nasdaq): symbol, side ('B'/'S'), imbalance (shares), paired_shares, ref_price?, near_price?, far_price?, clearing_price? (NYSE), cont_book_clearing_price?, closing_only_clearing_price?, market_imbalance? (Arca), cross_type? ('O'/'C', Nasdaq), transaction_type?, exchange?, ts_event, ts_init, time?/time_utc? — venue-specific fields are None when the venue doesn't publish them

Results & Reports

run_backtest(...) returns a Run handle — your single accessor for status and results, local or remote.

python
run.report()          # -> PerformanceReport
run.positions()       # positions over time   (DataFrame)
run.trades()          # executed trades        (DataFrame)
run.daily_returns()   # daily P&L              (DataFrame)
run.tearsheet()                      # -> '<run>.pdf' in the cwd (PDF, the default)
run.tearsheet(output='report.html')  # explicit HTML file (format from the extension)

# silent=True is the DEFAULT — run_backtest deploys and returns the Run immediately:
run = hf.run_backtest(...)
run.wait(progress=False)                          # block quietly until terminal (no tqdm bar)
report = run.report()
# reattach later from any machine (results outlive your Python process):
report = hf.get_run(run.run_id).wait(progress=False).report()
run_backtest(...) always returns a Run handle, never a bare report. silent=True is the default (deploy and return immediately); silent=False blocks with a live progress bar (interactive only). Calling run.wait() without progress=False renders a tqdm bar that pollutes captured/scripted output — always pass progress=False in scripted or agent-driven runs.
On a healthy run, run.report() is the only output you need. Do not poll status, stream progress, or fetch run.logs() / run.event_logs() routinely. If a run misbehaves, re-run once with config={'hiveq_log_level': 'DEBUG'}, inspect run.logs(), then remove the override before delivering the strategy.

PerformanceReport

python
report = run.report()
print(report.return_stats.to_string())   # Sharpe, Sortino, vol, drawdown, win rate

stats = report.summary_stats()           # dict: Sharpe, CAGR, Max Drawdown, ...
html = report.create_tearsheet()         # inline HTML string (Jupyter/Marimo) — to save a file use run.tearsheet()
Report DataFrames (fills, orders, …) can be None/empty — always guard before use. Tearsheets and summary_stats() are powered by quantstats, bundled with the SDK. Reader endpoints default to a 100k-row limit (raise hiveq_data_page_size for very dense schemas like options), so long backtests aren't silently truncated.
A completed backtest with zero trades is not a deliverable. Check report.return_stats["Total Trades"]; if it is zero or implausibly low, diagnose the entry logic and re-run until the strategy trades realistically within the requested window.

Function Registry

A versioned store of reusable Python callables that live on the platform. Write a function once, push_function it (with its requirements and a semver version), then fetch and run it from any script or machine by name and version.

python
import hiveq.flow as hf

def zscore(series, window=20):
    """Rolling z-score."""
    import numpy as np
    return (series[-1] - np.mean(series[-window:])) / np.std(series[-window:])

# register once...
hf.push_function(zscore, version="1.0.0", requirements=["numpy"])

# ...then load it back and run it on the platform from anywhere:
fn = hf.load_function("zscore", version="1.0.0")
hf.run_function(fn, [1, 2, 3, 4, 5], window=3, requirements=["numpy"])  # -> value
Access control is still TBD. Functions live in your own namespace by default; namespace="default" is the shared/public namespace. Treat cross-namespace permissions as subject to change.

Markets & Examples

Equities, futures (incl. continuous contracts with automatic rollover), options, and your own custom data feeds. Each is demonstrated by a complete, runnable example in the SDK's examples/ directory.

Equities
intraday_momentum_equity.py
SMA crossover, per-symbol state, EOD-flat
Futures
futures_continuous_rollover.py
continuous ES.c.0, enable_auto_rollover, on_rollover
Options (0DTE)
options_0dte_iron_condor.py
subscribe_option_snaps, multi-leg structure
Custom data
custom_data.py
bring-your-own CSV feed → on_custom_data
Orders & brackets
bracket_stop_take_profit.py
stop+target children, OCO emulation
Executors
executor_pov_sliced.py
POV executor, re-target in place
Pairs / stat-arb
pairs_stat_arb.py
rolling z-score spread, short_order
Scheduling
timers_scheduling.py
set_timer + ctx.now() ET checks
python
# Futures: subscribe to a continuous contract; rollover is automatic.
class Breakout:
    def on_start(self, ctx, event):
        ctx.subscribe_futures_bars(symbols=['ES.c.0'], interval='1m')
    def on_rollover(self, ctx, event):
        roll = event.data()   # Rollover: prev_contract -> current_contract

run = hf.run_backtest(
    strategy_configs=[StrategyConfig(name='Breakout', type='Breakout')],
    symbols=['ES.c.0'],
    start_date='2024-01-01', end_date='2024-03-01',
    data_configs=[{'type': 'hiveq_historical', 'dataset': 'HIVEQ_US_FUT',
                   'schema': ['bars_1m']}],
    backtest_config=BacktestConfig(enable_auto_rollover=True),
)

Enums & Config

Imports cheat-sheet

python
import hiveq.flow as hf
from hiveq.flow import StrategyConfig, BacktestConfig, EngineConfig, AssetType, get_run
from hiveq.flow.config import EventType, EventLogType, OMSType
from hiveq.flow.logger import logger as _get_logger
from hiveq.flow.trading_types import OrderType, OrderSide, OrderStatus, MarketCenter
from hiveq.flow.trading.price_utils import adjust_tick_size, get_min_tick

Key enums

  • AssetType: EQUITY · OPTIONS · FUTURES · CRYPTO · INDEX
  • OrderType: MARKET · LIMIT · STOP · STOP_LIMIT · MOO · MOC · LOO · LOC
  • OrderSide: BUY · SELL · OrderStatus: PENDING · SUBMITTED · ACCEPTED · REJECTED · CANCELED · FILLED · PARTIALLY_FILLED

BacktestConfig (key fields)

python
BacktestConfig(
    initial_capital=1_000_000.0,
    commission=0.001,
    equity_fee=0.0011,        # per share
    futures_fee=0.5,          # per contract
    session_start=None,       # ET "HH:MM"
    session_end=None,
    enable_auto_rollover=False,
    auto_flatten_at_close=False,  # force-close non-option positions at session close
    enable_tca=False,
)

Pitfalls & Patterns

HiveQ Flow is deliberately lean on authoring conveniences — these are the things that are easy to get wrong, and the idioms to handle them.

  • No built-in indicators or history buffer — keep your own collections.deque window and compute with numpy/pandas.
  • No native brackets/OCO — place protective child orders on fill in on_order and cancel siblings yourself.
  • No percent-of-equity sizing — size in fixed quantity (or off ctx.portfolio().equity and a price you track).
  • Timestamps ts_event/ts_init are nanoseconds — use .time / ctx.now() for datetimes.
  • Set symbols/start_date/end_date in one place (top-level args OR BacktestConfig), not both.
  • On a continuous-futures subscription, bar.symbol is the resolved outright contract (e.g. ESZ5), never the ES.c.0 string you subscribed with — so if bar.symbol != 'ES.c.0' silently drops every bar. Key off the root, or place orders on bar.symbol directly.
  • End-of-day flatten is opt-in, not automatic — set BacktestConfig(auto_flatten_at_close=True) to force-close non-option positions at session close (options settle automatically regardless).

Bracket / stop-loss + take-profit

python
def on_order(self, ctx, event):
    o = event.data()
    if o.is_filled and o.symbol == self.entry_symbol and not self.protected:
        entry = o.avg_px
        ctx.sell_order(o.symbol, quantity=o.filled_qty,
                       order_type=OrderType.STOP,  stop_price=entry * 0.98)
        ctx.sell_order(o.symbol, quantity=o.filled_qty,
                       order_type=OrderType.LIMIT, limit_price=entry * 1.04)
        self.protected = True
    # when one leg fills, cancel_all_orders(symbol) to emulate OCO

Ready to prove your alpha?

Build, optimize, deploy, and scale — on institutional-grade infrastructure.

Get Early Access

Revisions

  • 2026-07-21v0.3.7Synced to the published 0.3.7 contract: mandatory HiveQ logger instrumentation, explicit data_configs, quiet run.wait(progress=False), report-first output, diagnostic-only log retrieval, and zero-trade run validation.
  • 2026-07-16v0.3.6Re-synced to the consolidated docs/llms.txt: added on_imbalance / ImbalanceData, the FIX-style order-lifecycle contract (status vs events, cancel/replace race), silent-by-default run_backtest (run.wait(progress=False) → run.report()), and clarified there is no subscribe_futures_quotes.
  • 2026-07-08v0.3.6Synced to SDK 0.3.6 — auto-flatten-at-close option, continuous-futures bar.symbol pitfall, and clarified tearsheet file vs. inline API.
  • 2026-07-07v0.3.5Synced to SDK 0.3.5 — automatic browser sign-in (no manual API key) and tearsheet exports by file extension.
  • 2026-06-10v0.3.4Product overview added; AssetType re-exported from hiveq.flow.config.
  • 2026-06-09v0.3.4First published API reference.