Skip to content

Getting started

This page walks through installing Strix and instrumenting a small algorithm end-to-end. By the end you'll have a bot that records orders, books fills into positions, and reconciles its view against the broker.

Install

Strix targets Python 3.10+. The PyPI distribution name is strixrun (the import name stays strixpip install strixrun, then import strix). No PyPI release yet, so for now install from the source tree:

pip install ./sdk/python

Verify:

import strix
print(strix.__all__)

Your first instrumented algo

The shape of a Strix-instrumented algo is always the same:

  1. Call strix.init(...) once at startup.
  2. Wrap every order submission in with strix.order(...) as o: and submit using o.order_id.
  3. Pipe broker fills through strix.ingest_execution(...).
import strix
from strix import Side, Execution, RiskSettings


def submit_to_broker(order_id: str, symbol: str, side: Side, qty) -> None:
    # Replace with your broker client.
    print(f"[broker] submit {order_id} {symbol} {side} qty={qty}")


def on_broker_fill(order_id: str, symbol: str, side: Side, qty, price) -> None:
    strix.ingest_execution(Execution(
        order_id=order_id, symbol=symbol, side=side, qty=qty, price=price,
    ))


# 1. Initialize a session with risk limits. Capture the returned Session
#    when you need session_id (logging, dashboards, cross-system attribution).
session = strix.init(
    risk=RiskSettings(
        max_qty_per_order=1000,
        max_open_orders=20,
        position_limits={"AAPL": 500, "MSFT": 1000},
    ),
)
# strix.resume(...) returns a Session the same way.

# 2. Submit an order.
with strix.order(symbol="AAPL", side=Side.BUY, qty=100) as o:
    submit_to_broker(o.order_id, o.symbol, o.side, o.qty)

# 3. Fill arrives from the broker (synchronously here; usually async in real code).
on_broker_fill(o.order_id, "AAPL", Side.BUY, qty=100, price="150")

# Inspect state.
print(strix.positions())     # [Position(symbol='AAPL', qty=Decimal('100'), avg_price=Decimal('150'), last_price=Decimal('150'))]
print(strix.open_orders())   # []  — the AAPL order is FILLED, not open

Numeric inputs

Anywhere Strix accepts a quantity or price (qty, limit_price, price, RiskSettings.max_qty_per_order, RiskSettings.position_limits values) it accepts a Decimal, an int, or a numeric str. Internally everything becomes Decimal — no floats.

strix.order(symbol="AAPL", side=Side.BUY, qty=100)            # int OK
strix.order(symbol="AAPL", side=Side.BUY, qty="100.5")        # string OK
strix.order(symbol="AAPL", side=Side.BUY, qty=Decimal("100")) # Decimal OK
# strix.order(symbol="AAPL", side=Side.BUY, qty=100.5)        # float NOT OK — TypeError

Floats are rejected by design: they're the wrong type for monetary values, and silently round-tripping them through Decimal would hide precision bugs.

Order IDs

By default Strix generates a UUIDv7 for each order (time-ordered, sortable). Pass order_id=... to use your own. This is the right move when:

  • Your broker assigns its own ID and you want to keep the two in sync.
  • You're replaying historical trades and want stable IDs.
  • You're attributing orders across systems and want a deterministic key.
with strix.order(
    symbol="AAPL", side=Side.BUY, qty=100,
    order_id=f"my-algo-{strategy_id}-{seq}",
) as o:
    broker.submit(o.order_id, o.symbol, o.side, o.qty)

Order IDs must be globally unique inside a session — registering the same ID twice raises StrixDuplicateOrderIdError (also a ValueError for back-compat). Open orders carry forward across strix.init(...), so a still-open id stays taken in the next session; terminal orders drop, so their ids become reusable.

The order_id contract: any non-empty string up to 128 characters, no ASCII control characters (no \n, \r, \t, etc.). Anything beyond that — letters, digits, punctuation, Unicode — is fine. If you care about cloud-DB index locality (when CloudTransport lands), prefer time-ordered schemes like UUIDv7, ULID, or Snowflake.

What's not happening here

Strix is not your broker client. The with strix.order(...) block does not send the order anywhere — you call your broker inside the block. Strix records that you tried, runs pre-trade risk checks against the recorded state, and tracks lifecycle transitions.

That separation is deliberate: Strix never sits in the order path, so it can't be the reason your order misses the market. If your broker call raises, Strix marks the order REJECTED with the exception message so you can see what happened — but it didn't catch the order on the way out.

Next steps

  • Order lifecycle — what PENDING_NEW → NEW → REJECTED → … actually means.
  • Risk controls — configure and react to pre-trade checks.
  • Persistence — survive process restarts with LocalTransport.