API reference¶
The full public surface of strix. Signatures, parameters, and behavior — pulled directly from the source where docstrings exist, supplemented by hand where they don't.
The reference is generated by mkdocstrings when the docs site is built. Below each section is the directive that drives it; if you're reading the raw Markdown, those render as full signature blocks on the built site.
Top-level functions¶
strix.init¶
Start a NEW session.
By default, if transport has an open prior session it is closed and the
new session is seeded by carrying forward its open orders and positions.
Pass initial_state=InitialState(...) to override carry-forward with
an explicit snapshot. The override is atomic: both positions and open
orders are taken from initial_state. InitialState() with no
arguments starts the session completely flat.
risk configures pre-trade risk checks for this session. Defaults to
no limits.
config configures session-wide behavior policies (how to react to
invalid executions, missing market data). Defaults to
:class:SessionConfig with all-default values. Each init call
receives a fresh config — the prior session's config is NOT carried
forward (re-supply it if you want the same behavior).
market_data is the :class:MarketDataAdapter used for pre-trade
checks that need a live price (notional cap on market orders, price
thresholds). Required if any such check is configured in risk AND
you use market orders; otherwise optional. Not persisted — supply on
every init / resume call.
When transport is omitted, a fresh InMemoryTransport is created for
this session — no cross-call persistence. Hold a reference to an
InMemoryTransport instance across calls if you want carry-forward
without disk persistence.
Accepts transport, initial_state, risk, config, and market_data —
all keyword-only. See SessionConfig for the config
argument's shape; the rest are covered in the dedicated guides linked
elsewhere on this page.
strix.resume¶
resume accepts transport and market_data. initial_state and risk are not valid here — they apply only to init. Resumed state (including the risk policy in effect when the session was started) is reconstructed from the event log. market_data is a live source, not persisted, so it must be re-supplied on every resume call where Phase 2 risk checks need a quote.
Continue the existing open session in transport.
market_data is the :class:MarketDataAdapter used for pre-trade
checks that need a live price. Not persisted — supply it on every
resume call where you need such checks.
Raises NoActiveSessionError if there is no open session.
strix.order¶
Context-managed order submission.
Records the order in PENDING_NEW, runs pre-trade risk checks, yields the Order for broker submission, then transitions to NEW on clean exit or REJECTED (with reject_reason) on exception.
When order_id is omitted, the SDK generates a UUIDv7 — collision
is astronomically improbable. When the caller supplies an explicit
order_id that's already registered in the active session's order
book, raises :class:StrixDuplicateOrderIdError (also a
:class:ValueError for back-compat). The order book retains both
open and terminal orders for the lifetime of a session, so an id is
not reusable once registered, even after the order ends. Across
sessions, carry-forward brings open orders' ids into the new
session; terminal orders do not carry forward, so their ids become
reusable.
strix.cancel¶
Context-managed cancel submission.
Transitions the order to PENDING_CANCEL on entry, yields for the
caller to submit the cancel to their broker, then transitions to
CANCELLED on clean exit. On exception, the order is reverted to its
prior status and a CancelAttemptFailed event is emitted for audit.
The order must be in a cancellable status (PENDING_NEW, NEW, or
PARTIALLY_FILLED). Cancelling a terminal or already-pending-cancel
order raises :class:StrixOrderNotCancellableError (also a
:class:ValueError for back-compat). An unknown order_id raises
:class:StrixUnknownOrderError (also a :class:KeyError for
back-compat). Catch the umbrella :class:StrixCancelError to handle
both with one branch.
If an execution arrives during the block (a fill-wins race per FIX semantics), the cancel becomes a no-op on exit — broker reality wins.
Validation errors (raised before any state transition):
| Condition | Exception |
|---|---|
order_id not in the order book |
StrixUnknownOrderError (subclass of both StrixCancelError and KeyError) |
Order is in a non-cancellable status (FILLED, REJECTED, CANCELLED, PENDING_CANCEL) |
StrixOrderNotCancellableError (subclass of both StrixCancelError and ValueError) |
| No active session (init/resume not called) | RuntimeError |
Catch StrixCancelError to handle both validation failures together (typical when racing with concurrent fills), or the specific subclass when the distinction matters. Existing handlers using bare except KeyError: / except ValueError: keep working unchanged — both subclasses inherit from their stdlib counterpart for back-compat.
Cancellable prior statuses are PENDING_NEW, NEW, and PARTIALLY_FILLED. On clean exit, status transitions to CANCELLED. On body exception, status reverts to the prior value and a CancelAttemptFailed event is appended.
strix.set_risk¶
Replace the active session's pre-trade risk policy.
The whole policy is replaced atomically — every field of risk becomes
the new truth. Pass RiskSettings() with no arguments to clear all
limits. Partial updates (e.g. changing only max_qty_per_order) are
intentionally not supported: build a new RiskSettings carrying the
full desired policy.
The new policy applies to the next order to enter strix.order(...).
Orders already past the risk check (status NEW or PARTIALLY_FILLED)
are not re-evaluated; risk is pre-trade, not continuous.
Emits a RiskSettingsChanged event so the policy is persisted and
survives strix.resume.
Raises RuntimeError if no session is active.
Replaces the active session's risk policy atomically. The new policy applies to the next order entering strix.order(...); orders already past the risk check (status NEW or PARTIALLY_FILLED) are not re-evaluated. Emits a RiskSettingsChanged event so the policy survives strix.resume.
| Condition | Exception |
|---|---|
| No active session (init/resume not called) | RuntimeError |
risk is not a RiskSettings instance |
TypeError |
strix.ingest_execution¶
Apply an execution to the current session's orders and positions.
Returns the matching order's resulting :class:OrderStatus so callers can
distinguish partial fills from completions without re-querying
:func:open_orders. Specifically:
OrderStatus.PARTIALLY_FILLED— the order has more qty outstanding.OrderStatus.FILLED— the order is now fully filled.- The order's prior status (e.g.
NEW) — when the execution was an anomaly (mismatched symbol/side/qty, or applied to a terminal order): the position book reflects the broker's reality but the order book is deliberately untouched, so its status didn't change. None— when the execution was a duplicate (already-seenexecution_id) or referenced an unknownorder_id.
Anomaly handling (status returned but StrixInvalidExecutionError
raised under the default on_invalid_execution="raise" policy) is
described in :class:StrixInvalidExecutionError.
Invalid-execution categories. The execution's position effect is applied to the position book regardless (broker authoritative) and an ExecutionAnomalyDetected event is appended. SessionConfig.on_invalid_execution decides whether ingest_execution raises:
| Condition | Category | Default policy ("raise") |
|---|---|---|
order_id not in the order book |
missing-order |
StrixInvalidExecutionError |
execution.symbol ≠ order symbol |
symbol-mismatch |
StrixInvalidExecutionError |
execution.side ≠ order side |
side-mismatch |
StrixInvalidExecutionError |
Order is FILLED, REJECTED, or CANCELLED |
terminal-order |
StrixInvalidExecutionError |
filled_qty + execution.qty > order.qty (overfill) |
overfill |
StrixInvalidExecutionError |
Other errors:
| Condition | Exception |
|---|---|
execution is not an Execution instance |
TypeError |
| No active session (init/resume not called) | RuntimeError |
order_id and Execution.order_id must satisfy the order_id contract: non-empty, ≤128 characters, no ASCII control characters. Violations raise ValueError from the Order / Execution constructor.
Anomaly categories typically indicate caller bugs (malformed broker integration, mapping mistake) — but the broker has already moved the position, so Strix records the effect rather than dropping the fill. See order lifecycle § Validation for the conceptual breakdown.
strix.mark_to_market¶
Update the active session's mark cache.
Pass a mapping of {symbol: mark_price}. Each entry replaces the
cached mark for that symbol; symbols not in the payload are left
unchanged. Marks feed unrealized-P&L computation for stop-loss
evaluation (Phase 2 risk).
Empty mappings are a no-op. Numerics are normalized to Decimal.
Marks are ephemeral runtime state — no per-tick mark event is written
to the log. Instead, applying marks may emit a throttled PnLSnapshot
(see :class:SessionConfig.pnl_snapshot_every_seconds). On
:func:strix.resume, the mark cache starts empty and repopulates as
the caller resumes feeding marks via this function — the configured
:class:MarketDataAdapter must be re-supplied for any pre-trade check
that needs a live price.
Use :func:strix.active_symbols to discover which symbols Strix needs
marks for.
Raises RuntimeError if no session is active. Raises TypeError
if prices is not a mapping.
Updates the session's mark cache. Feeds unrealized-P&L computation for stop-loss evaluation. Empty mapping is a no-op. Numerics are normalized to Decimal. Marks are ephemeral runtime state — no per-tick event is written; on strix.resume the cache starts empty and repopulates as the caller resumes feeding marks. The cache update may emit a throttled PnLSnapshot (see SessionConfig.pnl_snapshot_every_seconds).
When RiskSettings.stale_mark_threshold is set, logs a WARNING for each active symbol not in the payload. Use strix.active_symbols() to discover which symbols need marks.
| Condition | Exception |
|---|---|
| No active session | RuntimeError |
prices is not a mapping |
TypeError |
strix.active_symbols¶
Symbols Strix needs prices/marks for right now.
A symbol is "active" if either:
- It has a non-zero net position (long or short), or
- It has at least one order in an open status (
PENDING_NEW,NEW,PARTIALLY_FILLED,PENDING_CANCEL).
Symbols with only terminal orders (FILLED, REJECTED, CANCELLED)
and a flat position drop out of the set. Symbols that have never been
touched are not included.
Use this to filter strix.mark_to_market(...) payloads to the minimum
set of symbols required for continuous-risk evaluation to be accurate.
Raises RuntimeError if no session is active.
Returns the set of symbols Strix needs marks for: non-zero net position OR at least one order in an open status (PENDING_NEW/NEW/PARTIALLY_FILLED/PENDING_CANCEL). Derived from in-memory state; no watchlist API. Use to filter mark_to_market payloads.
| Condition | Exception |
|---|---|
| No active session | RuntimeError |
strix.resume_trading¶
Clear every active stop-loss halt and resume trading immediately.
Manual override for when the operator needs to act before auto-recovery fires (e.g. recovery threshold was never set, or marks aren't improving fast enough). Global in scope — clears session-stop and every per-symbol cost-based halt in one shot.
reason is recorded in the audit event for later investigation.
No-op when nothing is halted. Otherwise emits a
StopLossManuallyOverridden event so the override is auditable on
replay.
The stop-loss configuration is unchanged. If the underlying P&L
condition still violates the threshold, the next execution or
mark_to_market call will re-trigger the same halt. Place the action
you need promptly.
Raises RuntimeError if no session is active.
Manual override for stop-loss halts. Global scope — clears every active halt (session and per-position) in one shot. Emits StopLossManuallyOverridden carrying the snapshot of cleared halts + the optional reason for audit. No-op when nothing is halted.
Does not re-evaluate continuous risk after clearing. If the underlying P&L condition still violates the threshold, the next execution or mark_to_market call will re-trigger. The override gives you a window to act; it doesn't disable the stop.
| Condition | Exception |
|---|---|
| No active session | RuntimeError |
strix.reconcile¶
strix.reconcile(
broker: BrokerReadAdapter,
/,
*,
since: str | None = None,
on_mismatch: Literal["raise", "warn", "trust"] = "raise",
check_positions: bool = True,
check_open_orders: bool = True,
) -> ReconcileResult
Reconcile the active session's state against a broker via :class:BrokerReadAdapter.
Pulls missed executions and (optionally) sanity-checks the resumed
positions and open orders against the broker's current view. Replaces
the manual gap-fill loop from docs/guides/crash-recovery.md.
Parameters¶
broker
A user-owned object implementing :class:BrokerReadAdapter. Strix ships
no production broker integrations; the user wires their own broker
SDK in here.
since
Adapter-defined cursor passed verbatim to
:meth:BrokerReadAdapter.fetch_executions. None (default) means
"use the marker recorded by the previous reconcile call in this
session" (or None if no prior call). Explicit values override
the recorded marker for this call only.
on_mismatch
How to handle position / open-order divergence:
- ``"raise"`` (default): raise :class:`BrokerReconciliationError`
carrying the full mismatch lists.
- ``"warn"``: log per-mismatch warnings, return the result, do
not raise.
- ``"trust"``: emit ``PositionAdjusted`` events to adopt the
broker's view of positions, and auto-cancel Strix-open orders
that the broker no longer reports as open. Other order
mismatch cases (status divergence, broker-only orders) are
NOT auto-corrected; if any remain, the call still raises
:class:`BrokerReconciliationError` at the end. To suppress
the raise even on unresolved trust-mode mismatches, use
``on_mismatch="warn"`` instead.
check_positions
When False, skip the position diff entirely.
check_open_orders
When False, skip the open-order diff entirely.
Returns¶
:class:ReconcileResult
Counts of executions ingested / deduped, plus the full mismatch
lists (each entry tagged with resolution indicating what
reconcile did about it) and the marker recorded for this call.
Raises¶
RuntimeError
No active session — call :func:init or :func:resume first.
:class:BrokerFetchError
The adapter raised inside a fetch_* call. __cause__ holds
the original exception.
:class:BrokerReconciliationError
Mismatches were detected under on_mismatch="raise" (any
mismatch) or on_mismatch="trust" (an unresolved order
mismatch).
Cross-session caveat¶
Execution dedupe is intra-session. If you reconcile in session A,
close A, start session B and reconcile again, the broker may return
executions that A already ingested; B's dedupe set is empty so they
would be applied again, double-counting positions. Mitigation: pass
since= accurately enough that the broker doesn't return already-
ingested executions across the session boundary.
Reconciles the active session against a BrokerReadAdapter: pulls missed executions (deduped by execution_id), then sanity-checks resumed positions and open orders against the broker's current view. Returns a ReconcileResult carrying counts and mismatch lists; raises BrokerReconciliationError on detected divergence by default. See the crash recovery guide for the cookbook.
since defaults to the marker recorded by the previous successful reconcile call in this session (or None if no prior call). An explicit value overrides the recorded marker for this call only.
| Condition | Exception |
|---|---|
| No active session (init/resume not called) | RuntimeError |
The adapter's fetch_* method raised |
BrokerFetchError (__cause__ holds the original) |
Mismatches detected under on_mismatch="raise", or unresolved order mismatches under on_mismatch="trust" |
BrokerReconciliationError |
strix.positions¶
Snapshot of all positions known to the current session.
Includes zero-quantity entries for symbols that were previously held and are now flat — callers filter if they want non-zero only.
strix.open_orders¶
Snapshot of orders in open statuses: PENDING_NEW, NEW, PARTIALLY_FILLED, PENDING_CANCEL.
PENDING_CANCEL counts as open because the broker still has the order — a
fill can still arrive before the cancel acks (FIX 4.2 scenario D4).
Terminal orders (FILLED, REJECTED, CANCELLED) are excluded from this view
but remain in the event log for audit — use :func:get_order to look
them up by id.
strix.get_order¶
Look up the current state of an order by id.
Returns the latest :class:Order snapshot for order_id, or None
if no such order exists in the active session's order book.
Symmetric with :func:positions and :func:open_orders. Unlike
:func:open_orders, which excludes terminal statuses, this accessor
returns orders in any status — FILLED, REJECTED,
CANCELLED included.
The typical use is reading state after a strix.order(...) block
has exited: the snapshot yielded inside the block is frozen at
PENDING_NEW, and the post-block transition lives in the order
book rather than mutating the yielded object.
.. code-block:: python
with strix.order(symbol="AAPL", side=Side.BUY, qty=100) as o:
broker.submit(o.order_id, o.symbol, o.qty)
current = strix.get_order(o.order_id)
assert current is not None and current.status is OrderStatus.NEW
Raises RuntimeError if no session is active.
Returns the current :class:Order snapshot for the given order_id, or None when no such order exists in the active session. Unlike :func:open_orders (open statuses only), :func:get_order reaches orders in any status — including terminal FILLED / REJECTED / CANCELLED. Use it to read state after a strix.order(...) block has exited, since the snapshot yielded inside the block stays frozen at PENDING_NEW.
| Condition | Exception |
|---|---|
| No active session (init/resume not called) | RuntimeError |
strix.replay¶
strix.replay(
*,
data_dir: str,
session_id: str | None = None,
follow: bool = False,
poll_interval: float = 0.5,
) -> Iterator[SessionEvent]
Read-only consumer-side event reader. Yields parsed SessionEvent instances from a LocalTransport data_dir without acquiring the lock, so a sidecar monitor can run concurrently with a writer process (algo, backtest, etc.). The documented JSONL audit format is the contract; consumers do not need to parse JSON themselves.
session_id=None(default) reads the session named in<data_dir>/active_sessionand raisesNoActiveSessionErrorif none is open.session_id="<uuid>"reads a specific session — including closed ones — for offline analysis.follow=Truekeeps the file open and polls for new lines (tail-style), yielding new events as they're appended.poll_intervalcontrols poll cadence.
Use isinstance(evt, PnLSnapshot) (or any other event class — all exported on strix) to filter. Typical pattern:
import strix
from strix import PnLSnapshot, StopLossTriggered
for evt in strix.replay(data_dir="./strix_data", follow=True):
if isinstance(evt, PnLSnapshot):
...
elif isinstance(evt, StopLossTriggered):
...
| Condition | Exception |
|---|---|
data_dir missing or session has no events.jsonl |
FileNotFoundError |
session_id=None and no active session recorded |
NoActiveSessionError |
.strix-storage marker carries an unsupported format_version |
StorageVersionError |
A line is malformed JSON or carries an unknown event type |
StorageCorruptError |
strix.active_session_id¶
Read-only accessor for the session id recorded in <data_dir>/active_session. Returns None if no session is open or the data_dir doesn't exist. Does not acquire the storage lock. Useful for tooling that needs to correlate event-log reads with the active session without opening a LocalTransport handle.
strix.list_sessions¶
Read-only listing of every session id in a LocalTransport data_dir, oldest first. Both open and closed sessions are included. Returns an empty list when the data_dir doesn't exist or contains no sessions yet. Does not acquire the storage lock — safe to call alongside a writer process.
Order is chronological — session ids are UUIDv7, so lexicographic sort matches creation order. Pair with strix.replay(data_dir=..., session_id=...) to walk each session's events for audit, replay, or analytics:
import strix
from strix import CancelAttemptFailed
for sid in strix.list_sessions(data_dir="./strix_data"):
failures = [
e for e in strix.replay(data_dir="./strix_data", session_id=sid)
if isinstance(e, CancelAttemptFailed)
]
if failures:
print(sid, len(failures), "cancel attempts failed")
Domain types¶
Side¶
Bases: str, Enum
Order/execution direction. BUY increases position, SELL decreases it.
OrderStatus¶
Bases: str, Enum
Order lifecycle states.
PENDING_NEW: created and risk-checked, broker not yet acked.NEW: broker accepted (clean exit from thestrix.orderblock).REJECTED: pre-trade risk check failed, or the user code raised inside the order block.PARTIALLY_FILLED: at least one execution applied, more qty outstanding.FILLED: cumulative fills equal order qty. Terminal.PENDING_CANCEL: cancel requested viastrix.cancel, broker not yet acked. Still counts as open at the broker.CANCELLED: broker confirmed cancel (clean exit from thestrix.cancelblock). Terminal.
Aligned with FIX 4.2 OrdStatus semantics.
Order¶
@dataclass(frozen=True)
class Order:
order_id: str # required; UUIDv7 by default when omitted from strix.order(...)
symbol: str # required; non-empty
side: Side # required; BUY or SELL
qty: Decimal # required; positive; Numeric accepted
limit_price: Decimal | None = None # None = market-style from Strix's perspective
timestamp: datetime | None = None # caller-supplied; informational
filled_qty: Decimal = Decimal(0) # cumulative fills; non-negative
avg_fill_price: Decimal | None = None
status: OrderStatus = OrderStatus.PENDING_NEW
reject_reason: str | None = None # populated when status == REJECTED
Constructed by strix.order(...) in normal use. You only build Order directly when implementing BrokerReadAdapter.fetch_open_orders (broker-side view of working orders — populate status with NEW or PARTIALLY_FILLED; other statuses are Strix-internal).
An order tracked by Strix.
Created by :func:strix.order (the only supported path for new orders during
a live session). Immutable — status transitions and fill updates rebuild the
instance under the hood. Yielded inside the strix.order context for the
caller to submit to their broker.
Attributes:
| Name | Type | Description |
|---|---|---|
order_id |
str
|
Strix-assigned unique id (UUIDv7 by default; user-supplied via
the |
symbol |
str
|
Trading symbol (non-empty). |
side |
Side
|
BUY or SELL. |
qty |
Decimal
|
Order quantity (positive). |
limit_price |
Decimal | None
|
Optional limit price; |
timestamp |
datetime | None
|
Optional caller-supplied timestamp. |
filled_qty |
Decimal
|
Cumulative filled quantity. Starts at 0, grows with each applied execution. |
avg_fill_price |
Decimal | None
|
Volume-weighted average price across applied executions,
or |
status |
OrderStatus
|
Current :class: |
reject_reason |
str | None
|
Populated when |
Execution¶
@dataclass(frozen=True)
class Execution:
order_id: str # required; the Strix order_id this fill belongs to
symbol: str # required; must match the order's symbol
side: Side # required; must match the order's side
qty: Decimal # required; positive — direction comes from side
price: Decimal | None = None # optional; without price, Position.avg_price is not updated
timestamp: datetime | None = None # broker timestamp; informational
execution_id: str | None = None # broker fill id; required for dedupe in strix.reconcile
A single fill (or partial fill) reported by the broker.
Pass to :func:strix.ingest_execution to update the parent order and position book.
qty is the size of this fill (always positive — direction comes from side).
Multiple Execution objects can target the same order_id for partial fills.
Attributes:
| Name | Type | Description |
|---|---|---|
order_id |
str
|
Strix order_id this execution belongs to. |
symbol |
str
|
Trading symbol; must match the order's symbol. |
side |
Side
|
BUY or SELL; must match the order's side. |
qty |
Decimal
|
Filled quantity (positive). Decimal, int, or numeric string. |
price |
Decimal | None
|
Fill price (optional). Without price, position avg_price is left unchanged. |
timestamp |
datetime | None
|
Broker timestamp (optional, informational). |
execution_id |
str | None
|
Broker-supplied fill id (optional, currently informational). |
Position¶
@dataclass(frozen=True)
class Position:
symbol: str # required
qty: Decimal = Decimal(0) # signed: positive long, negative short, zero flat
avg_price: Decimal | None = None # None when flat or no priced fills
last_price: Decimal | None = None # price of the most recent execution on this symbol
Net position in a symbol.
Sign convention: positive qty is long, negative is short, zero is flat.
avg_price is the average entry price of the currently held size; it resets
when the position flips sign and is None when flat.
last_price is the price of the most recent execution that updated this
symbol — useful when a position is built across multiple partial fills and
avg_price (VWAP) doesn't reveal where the latest print landed.
Attributes:
| Name | Type | Description |
|---|---|---|
symbol |
str
|
Trading symbol. |
qty |
Decimal
|
Net signed quantity. Long positive, short negative. |
avg_price |
Decimal | None
|
Average entry price of the current position (None when flat or when no priced executions have arrived). |
last_price |
Decimal | None
|
Price of the most recent execution on this symbol. |
Session¶
Opaque handle returned by strix.init / strix.resume.
Holds in-memory projection of the event log: position book, order book, risk config, and the next-seq counter. Mutating operations emit events to the configured transport backend.
Session is an opaque handle returned by init and resume. The only public attribute is session_id: str (UUIDv7, useful for logging). All mutation goes through the module-level functions.
Configuration¶
InitialState¶
@dataclass(frozen=True)
class InitialState:
positions: tuple[Position, ...] = ()
open_orders: tuple[Order, ...] = ()
Explicit seed state for a new session.
Passing initial_state to :func:strix.init overrides the default
carry-forward of positions and open orders from any prior session. The
override is atomic: both fields are taken as the new session's starting
truth. To start completely flat, pass InitialState() (the defaults
are empty tuples).
Use this when:
- First run against a fresh
LocalTransportand you want to seed positions/orders from an external source. - Starting a paper-trading or backtest run that should ignore prior live state.
Attributes:
| Name | Type | Description |
|---|---|---|
positions |
tuple[Position, ...]
|
Positions to seed the new session with. Empty by default. |
open_orders |
tuple[Order, ...]
|
Open orders to seed the new session with. Empty by default. Caller is responsible for ensuring these match broker reality — Strix does not reconcile them on entry. |
RiskSettings¶
@dataclass(frozen=True)
class RiskSettings:
# Phase 1 — user-data only
max_qty_per_order: Numeric | None = None
min_qty_per_order: Numeric | None = None
max_orders: int | None = None
max_open_orders: int | None = None
max_shares_per_equity_symbol: Numeric | None = None
position_limits: Mapping[str, Numeric] = {}
# Phase 2 — market-data driven
max_open_notional: Numeric | None = None
max_share_price: Numeric | None = None
min_share_price: Numeric | None = None
min_share_price_short: Numeric | None = None
session_stop_loss: SessionStopLoss | None = None
cost_based_stop_loss: CostBasedStopLoss | None = None
# Enforcement mode for pre-trade limit breaches
on_breach: Literal["warn", "raise"] = "warn"
# Mark freshness
stale_mark_threshold: timedelta | None = timedelta(seconds=5)
on_breach decides how a pre-trade limit breach is handled. "warn" (default) records a RiskBreach event and logs a WARNING while the order still proceeds — Strix stays out of the order path. "raise" makes the breach raise StrixRiskError and record the order as REJECTED before the strix.order(...) block body runs. Stop-loss halts (StrixHaltedError) are not governed by on_breach — they always gate. See Risk controls.
SessionConfig¶
@dataclass(frozen=True)
class SessionConfig:
on_invalid_execution: Literal["raise", "warn", "silent"] = "raise"
on_missing_market_data: Literal["reject", "allow"] = "reject"
pnl_snapshot_every_seconds: float = 15.0
on_invalid_execution decides how strix.ingest_execution reacts when the execution doesn't match the local order book (orphan, symbol/side mismatch, terminal-order target, overfill). In all cases the execution's position effect is applied to the position book (broker authoritative) and an ExecutionAnomalyDetected event is emitted. The setting governs the additional response — raise StrixInvalidExecutionError, log a WARNING, or proceed silently. strix.reconcile suppresses the raise branch during its broker-fetch loop regardless.
on_missing_market_data decides how pre-trade risk checks react when the configured MarketDataAdapter cannot supply a price reference. "reject" treats missing data as a breach (then handled per RiskSettings.on_breach — raised in "raise" mode, recorded as a RiskBreach in the default "warn" mode); "allow" logs a WARNING and skips the affected check.
pnl_snapshot_every_seconds controls the minimum interval between consecutive PnLSnapshot events. Snapshots are emitted from mark_to_market and ingest_execution when the interval has elapsed and something has actually changed; force-flushed on StopLossTriggered, StopLossRecovered, and SessionEnded. For CloudTransport users this field is overridden at session start by the server based on the active plan; local-only users fully control it.
SessionStopLoss¶
@dataclass(frozen=True)
class SessionStopLoss:
threshold: Decimal # negative; e.g. Decimal("-5000")
recovery_threshold: Decimal | None = None # must be > threshold; None disables auto-recover
mode: Literal["total", "realized"] = "total" # total = realized + unrealized (needs marks)
Session-wide stop-loss configuration.
Continuous evaluation: when session P&L crosses threshold downward,
Strix halts the session (new orders raise :class:StrixHaltedError).
When P&L recovers past recovery_threshold (if set), Strix unhalts
automatically.
Attributes:
| Name | Type | Description |
|---|---|---|
threshold |
Decimal
|
P&L floor — typically a negative |
recovery_threshold |
Decimal | None
|
P&L value that must be reached/exceeded to
auto-unhalt. Must be |
mode |
Literal['total', 'realized']
|
|
CostBasedStopLoss¶
@dataclass(frozen=True)
class CostBasedStopLoss:
threshold_pct: Decimal # must be negative; e.g. Decimal("-0.10") for 10% loss
recovery_threshold_pct: Decimal | None = None # must be > threshold_pct; None disables P&L-based recovery
Per-position stop-loss based on drawdown as a fraction of cost basis.
Continuous evaluation: for every non-zero position, compute
pnl_pct = (mark - avg_price) * qty / (avg_price * abs(qty)). When
this falls at or below threshold_pct, halt that symbol
(orders on it raise :class:StrixHaltedError unless exposure-reducing).
When the position closes or pnl_pct recovers past
recovery_threshold_pct, the halt clears automatically.
Cost-based stop is always evaluated against unrealized P&L of the
currently-held position. Realized-only semantics don't fit per-position
(there is no position to halt once it's closed); use the session-wide
:class:SessionStopLoss with mode="realized" for realized-only
drawdown.
Attributes:
| Name | Type | Description |
|---|---|---|
threshold_pct |
Decimal
|
Fractional drawdown trigger — typically a negative
|
recovery_threshold_pct |
Decimal | None
|
Fractional drawdown above which an auto-
recovery fires. Must be |
Always evaluates unrealized drawdown on currently-held positions. Closing the position auto-clears the halt regardless of recovery_threshold_pct.
Pre-trade risk policy for a session.
Passed to :func:strix.init via the risk argument and to
:func:strix.set_risk for mid-session updates. All fields are optional;
an omitted field means "no limit on this dimension". The same
RiskSettings instance can be reused across multiple init calls.
Attributes:
| Name | Type | Description |
|---|---|---|
max_qty_per_order |
Decimal | None
|
Reject any order whose |
min_qty_per_order |
Decimal | None
|
Reject any order whose |
max_orders |
int | None
|
Reject a new order if the session has already attempted
this many orders. Counts every entry to |
max_open_orders |
int | None
|
Reject a new order if the session already has this
many orders in open status ( |
max_shares_per_equity_symbol |
Decimal | None
|
Global per-symbol position cap, applied
to every symbol not present in |
position_limits |
Mapping[str, Decimal]
|
Per-symbol overrides for the global cap. Where a
symbol is in this mapping, its value is used and the global
|
Market data (Phase 2)¶
MarketDataAdapter (Protocol)¶
class MarketDataAdapter(Protocol):
def last(self, symbol: str) -> Decimal | None: ...
def quote(self, symbol: str) -> Quote | None: ...
def mark(self, symbol: str) -> Decimal | None: ...
Bases: Protocol
Structural type for market-data sources.
All three accessors return None when no data is available for the
symbol. Strix interprets None according to the session's
SessionConfig.on_missing_market_data policy (default: reject).
Implementations are responsible for freshness. Strix calls these synchronously on the order-placement hot path and on every continuous-risk evaluation; cache aggressively if your underlying source is slow.
mark
¶
Mark price for P&L computation.
Often equal to last for actively-traded names, but may differ
for end-of-day marks, illiquid names, or after-hours sessions.
Implementations may default to last when no distinct mark is
maintained.
Structural type for market-data sources. All three accessors return None when no data is available; missing data is then interpreted per SessionConfig.on_missing_market_data for pre-trade checks (continuous-risk evaluation always skips with a WARNING).
Strix calls these synchronously on the order path and on every continuous-risk evaluation. Cache aggressively if your underlying source is slow.
Quote¶
Best bid / best ask snapshot for a symbol.
ts is informational — Strix uses the value when present (e.g. for
staleness warnings) but does not require it.
Attributes:
| Name | Type | Description |
|---|---|---|
bid |
Decimal
|
Best bid price. Must be |
ask |
Decimal
|
Best ask price. Must be |
ts |
datetime | None
|
Optional timestamp of the quote. |
Top-of-book snapshot. Construction validates bid <= ask.
StaticMarketData¶
StaticMarketData(
*,
prices: Mapping[str, Numeric] | None = None,
quotes: Mapping[str, Quote] | None = None,
marks: Mapping[str, Numeric] | None = None,
)
In-memory, dict-backed :class:MarketDataAdapter.
Useful for tests, replays, paper-trading runs against a fixed snapshot, or as the source for a thin user-built wrapper that writes from a WebSocket feeder thread and reads from Strix's sync risk path.
mark falls back to last for symbols where no explicit mark has
been set.
Example
from decimal import Decimal from strix import StaticMarketData, Quote md = StaticMarketData( ... prices={"AAPL": Decimal("150.10")}, ... quotes={"AAPL": Quote(Decimal("150.05"), Decimal("150.15"))}, ... ) md.last("AAPL") Decimal('150.10')
set_mark
¶
Update the mark price for symbol.
Setting an explicit mark overrides the mark fallback to last.
Dict-backed MarketDataAdapter for tests, replays, and simple use cases. set_last/set_quote/set_mark mutators allow incremental updates. mark() falls back to last() when no explicit mark is set.
Broker reconciliation¶
BrokerReadAdapter (Protocol)¶
class BrokerReadAdapter(Protocol):
def fetch_executions(self, *, since: str | None) -> ExecutionBatch: ...
def fetch_open_orders(self) -> Iterable[Order]: ...
def fetch_positions(self) -> Iterable[Position]: ...
Bases: Protocol
Structural type for broker integrations. Used for static type checking only.
Implementations are user-owned — every algo has its own broker SDK. The
Strix SDK ships only this Protocol and :class:StaticBrokerReadAdapter as a
test fixture.
Implementations MUST set their own per-request HTTP/socket timeouts in
the constructor or per-call. Strix does not wrap these methods in a
timeout — a hung adapter hangs strix.reconcile. The constraint that
network I/O must not block session-boundary calls indefinitely is
satisfied because init / resume never invoke the adapter;
reconcile is the only entry point, and it's user-explicit.
Exceptions raised by any method are wrapped in :class:BrokerFetchError
by strix.reconcile (the original exception is preserved via
__cause__).
fetch_executions
¶
Return all broker fills since the supplied since cursor.
since is an adapter-defined opaque marker. None means "from
the beginning of broker history that Strix should know about" — the
adapter decides how far back that is.
fetch_open_orders
¶
Return all orders currently open at the broker.
The adapter is expected to map the broker's status enum to Strix's
:class:OrderStatus. Status values should be in the open set
(PENDING_NEW, NEW, PARTIALLY_FILLED, PENDING_CANCEL);
terminal-state orders should not appear here.
fetch_positions
¶
Return current net positions held at the broker.
Symbols with zero net qty MAY be omitted (Strix treats absent symbols
as flat). avg_price is optional; not used to detect mismatches
but reported in :class:PositionMismatch for inspection and used by
trust mode to adopt the broker's average.
Structural type for broker integrations. User-implemented against the broker's SDK. Exceptions raised inside any fetch_* method are wrapped in BrokerFetchError by strix.reconcile (original exception preserved via __cause__). The adapter is responsible for its own per-request timeouts; Strix does not wrap calls in a timeout.
StaticBrokerReadAdapter¶
StaticBrokerReadAdapter(
*,
executions: Iterable[Execution] = (),
next_marker: str | None = None,
open_orders: Iterable[Order] = (),
positions: Iterable[Position] = (),
)
Test fixture. Returns pre-configured data without I/O.
Use in tests, examples, and dry-run / paper-trade harnesses. NOT a
production broker. since is accepted but ignored — the adapter
returns the full configured executions list every call, paired with
the configured next_marker (default None).
Frozen data on construction; if a test needs different data, construct
a new adapter. To exercise the :class:BrokerFetchError path, write a
one-off Protocol-conformant class inline.
Frozen-data test fixture. Returns the configured lists on every fetch_* call. since is accepted but ignored. Not a production broker — write a Protocol-conformant class wrapping your real broker SDK.
ExecutionBatch¶
@dataclass(frozen=True)
class ExecutionBatch:
executions: list[Execution]
next_marker: str | None = None
Adapter response for :meth:BrokerReadAdapter.fetch_executions.
executions is the (possibly empty) list of fills since the requested
marker. next_marker is the adapter-defined cursor representing
"everything up to and including this point" — Strix stores it opaquely
and feeds it back on the next fetch_executions call when the caller
doesn't pass an explicit since=.
next_marker SHOULD be populated even when executions is empty so
the cursor advances to the broker's current state and the next call
doesn't re-scan from the same point. None is legal (the recorded
marker simply stays None and the next call sends since=None).
Returned by BrokerReadAdapter.fetch_executions. next_marker is the adapter-defined opaque cursor representing "everything up to and including this point"; Strix stores it and passes it back on the next call when the caller doesn't supply since= explicitly.
PositionMismatch¶
@dataclass(frozen=True)
class PositionMismatch:
symbol: str
strix_qty: Decimal
broker_qty: Decimal
strix_avg_price: Decimal | None
broker_avg_price: Decimal | None
resolution: Literal["auto_adjusted", "unresolved"]
A position-book divergence between Strix's view and the broker's.
Both strix_qty and broker_qty are populated; either may be zero if
the symbol is missing from one side (Strix's book treats absent symbols
as flat). avg_price fields carry whatever each side reported; they
are not used to detect mismatches (avg-price drift is expected and
broker-specific) but are present so trust-mode can adopt the broker's
average.
resolution records what reconcile did about this mismatch:
"auto_adjusted":on_mismatch="trust"emitted aPositionAdjustedevent and the position book now matches the broker."unresolved":on_mismatch="raise"/"warn"modes always use this; the caller resolves manually.
A position-book divergence between Strix and the broker. avg_price fields are reported for inspection but do not trigger mismatches on their own. resolution="auto_adjusted" means trust-mode emitted a PositionAdjusted event to bring Strix's book in line with the broker; "unresolved" means the caller must handle it.
OrderMismatch¶
@dataclass(frozen=True)
class OrderMismatch:
order_id: str
strix_status: OrderStatus | None
broker_status: OrderStatus | None
resolution: Literal["auto_cancelled", "unresolved"]
An open-order divergence between Strix's view and the broker's.
Either side's status may be None if the order is unknown on that side.
resolution records what reconcile did:
"auto_cancelled": trust-mode emitted anOrderStatusChangedto mark the orderCANCELLED(only the Strix-open + broker-absent case is auto-corrected)."unresolved": raise/warn modes always; trust mode for cases outside the bounded auto-correct scope.
An open-order divergence. Either side's status may be None when the order is unknown there. resolution="auto_cancelled" covers the Strix-open + broker-absent case under trust mode; all other cases stay "unresolved".
ReconcileResult¶
@dataclass(frozen=True)
class ReconcileResult:
executions_ingested: int
executions_skipped_duplicate: int
marker: str | None
position_mismatches: list[PositionMismatch]
order_mismatches: list[OrderMismatch]
Returned by :func:strix.reconcile on successful completion.
marker echoes the next_marker returned by the adapter for this
call (also recorded internally via the ReconciliationCompleted event
so subsequent reconcile calls auto-resume from it).
Returned by strix.reconcile when the call completes without raising. marker echoes the cursor returned by the adapter (also persisted via the ReconciliationCompleted event so subsequent calls auto-resume from it).
Transport¶
Transport (Protocol)¶
A structural type — any object implementing the methods can be passed as transport. The two concrete implementations that ship are below.
Bases: Protocol
Structural type for Strix transports (the persistence/sync port). Used for static type checking only.
InMemoryTransport¶
Ephemeral, in-process event log. State lives until process exit. Default when strix.init() is called without a transport argument. Useful for tests, short scripts, and anything that doesn't need durability.
LocalTransport¶
File-backed JSONL event log. Survives process restart. data_dir is required — Strix won't pick a default location.
Disk layout under data_dir:
.strix-storage # marker: {"format_version": 1}
strix.lock # OS file lock; auto-released on process exit
active_session # one line: <session_id> of currently-open session
sessions/<session_id>/events.jsonl
Single-writer per data_dir (OS file lock). See Persistence for the full disk-format story.
Methods of interest to callers:
transport.close()— release the lock and close any open event log file. Auto-runs on process exit; eager call is fine.transport.flush()—fsyncthe currently-open event log file. Rarely necessary becauseappend()alreadyfsyncs.
Errors¶
All inherit from StrixError. Catch the base if you want a single handler; catch specifics if you want different reactions.
StrixError¶
Bases: Exception
Base class for Strix SDK errors.
StrixRiskError¶
Bases: StrixError
Raised when a pre-trade risk check blocks an order.
Raised when a pre-trade risk check blocks an order, inside the with strix.order(...) block before the body runs. The blocked order is recorded in the order book as REJECTED.
Attributes:
reason: str— human-readable reason (also the exception message).order_id: str— the Strix order_id of the blocked order.
StrixInvalidExecutionError¶
Bases: StrixError
Raised by :func:strix.ingest_execution when an execution does not
match the local order book and the session's
on_invalid_execution policy is "raise".
The position effect of the execution has already been applied to the
position book (the broker is authoritative on positions/cash) and an
ExecutionAnomalyDetected event has been written to the event log
before this exception is raised. Catching it does not undo those
effects.
Attributes:
| Name | Type | Description |
|---|---|---|
category |
Which kind of mismatch was detected. One of
|
|
detail |
Human-readable description of the mismatch. |
|
execution_id |
|
|
order_id_ref |
|
Raised by strix.ingest_execution when the execution does not match the local order book and SessionConfig.on_invalid_execution == "raise" (the default). The position effect has already been applied to the position book (broker authoritative) and an ExecutionAnomalyDetected event has been appended before the exception is raised — catching it does not undo those effects.
Attributes:
category: Literal["missing-order", "symbol-mismatch", "side-mismatch", "terminal-order", "overfill"]— which kind of mismatch was detected.detail: str— human-readable description.execution_id: str | None— broker-supplied fill id, if any.order_id_ref: str | None— theorder_idStrix tried to match.
StrixDuplicateOrderIdError¶
Bases: StrixError, ValueError
Raised by :func:strix.order when the caller-supplied order_id
is already registered in the active session's order book.
The order book retains both open and terminal orders for the lifetime of a session, so an id is not reusable once registered, even after the order ends. Across sessions, carry-forward brings open orders' ids into the new session; terminal orders do not carry forward, so their ids become reusable.
When order_id is omitted from :func:strix.order, the SDK
auto-generates a UUIDv7 and this error cannot fire; it is only
reachable when the caller passes an explicit order_id that
collides.
Inherits from :class:ValueError for back-compat with the original
raise type — existing except ValueError: handlers keep working
unchanged.
Attributes:
| Name | Type | Description |
|---|---|---|
order_id |
The colliding |
Raised by strix.order(...) when the caller-supplied order_id is already registered in the active session's order book. Inherits from StrixError and ValueError — existing except ValueError: handlers keep working unchanged.
Carry-forward semantics: open orders' ids carry forward into a new session via strix.init(...), so the id remains taken until the order terminates and the session boundary advances. Terminal orders are dropped from carry-forward; their ids become reusable in the next session.
Attributes:
order_id: str— the collidingorder_id.
StrixCancelError¶
Bases: StrixError
Base class for errors raised by :func:strix.cancel.
Two concrete subclasses cover the validation paths:
- :class:
StrixUnknownOrderError— no order with the givenorder_idexists in the session's order book. - :class:
StrixOrderNotCancellableError— the order exists but is in a status that doesn't permit cancellation (FILLED,REJECTED,CANCELLED, or alreadyPENDING_CANCEL).
Both subclasses also inherit from the stdlib exception they historically
were (KeyError and ValueError respectively), so existing
except (KeyError, ValueError): handlers continue to work unchanged.
New code should catch :class:StrixCancelError to handle both, or the
specific subclass when the discrimination matters.
Attributes:
| Name | Type | Description |
|---|---|---|
order_id |
The |
Base class for errors raised by strix.cancel(...). Two concrete subclasses cover the validation paths; catch StrixCancelError to handle both (the typical race-handling shape) or the specific subclass when discrimination matters. Each subclass also inherits from its historical stdlib counterpart (KeyError / ValueError) so existing handlers keep working unchanged.
Attributes:
order_id: str— theorder_idpassed tostrix.cancel(...).
StrixUnknownOrderError¶
Bases: StrixCancelError, KeyError
Raised by :func:strix.cancel when the order_id is not in the
session's order book.
Inherits from :class:StrixCancelError (catch-all for cancel errors) and
from :class:KeyError (back-compat with the original raise type — this
was the only way the cancel CM previously signalled an unknown order).
Raised when the order_id passed to strix.cancel(...) is not in the session's order book. Inherits from StrixCancelError and KeyError.
StrixOrderNotCancellableError¶
Bases: StrixCancelError, ValueError
Raised by :func:strix.cancel when the order exists but is in a
non-cancellable status.
Cancellable statuses are PENDING_NEW, NEW, and PARTIALLY_FILLED.
Any other status (terminal FILLED/REJECTED/CANCELLED or
already PENDING_CANCEL) triggers this error.
Inherits from :class:StrixCancelError (catch-all for cancel errors) and
from :class:ValueError (back-compat with the original raise type).
Attributes:
| Name | Type | Description |
|---|---|---|
current_status |
The order's status at the moment of the cancel call. |
Raised when the order exists but is in a status that doesn't permit cancellation (FILLED, REJECTED, CANCELLED, or already PENDING_CANCEL). Inherits from StrixCancelError and ValueError.
Attributes (in addition to order_id from the base):
current_status: OrderStatus— the order's status at the moment of the cancel call.
StrixHaltedError¶
Bases: StrixError
Raised when an order is blocked because a stop-loss is currently triggered.
Distinct from :class:StrixRiskError: a regular risk check rejection is a
per-order limit breach; a halt is a session-wide (or per-position) trading
suspension that persists until the configured recovery threshold is crossed
or :func:strix.resume_trading is called.
Attributes:
| Name | Type | Description |
|---|---|---|
reason |
Human-readable description. |
|
scope |
|
|
subject |
Symbol for |
|
order_id |
Strix order_id of the blocked order. |
Raised when an order is blocked because a stop-loss is currently triggered (Phase 2). Distinct from StrixRiskError: a halt is a session-wide (or per-symbol) trading suspension that persists until the configured recovery threshold is crossed or strix.resume_trading() is called. Exposure-reducing orders bypass the halt.
Attributes:
reason: str— human-readable reason.scope: str—"session"(session-wide stop) or"position"(per-symbol stop).subject: str | None— symbol forscope == "position";Nonefor session-scope.order_id: str— the Strix order_id of the blocked order.
StrixStorageError¶
Bases: StrixError
Base class for storage-related errors.
Base for storage-related errors. The concrete subclasses below all inherit from it.
NoActiveSessionError¶
Bases: StrixStorageError
resume() was called but no open session exists in storage.
Raised by strix.resume(transport=...) when the transport has no open session. Handle by falling through to strix.init(transport=...) for the fresh-start case — see crash recovery.
StorageCorruptError¶
Bases: StrixStorageError
Storage state is internally inconsistent (bad pointer, malformed event, unknown type, etc.).
Raised when storage state is internally inconsistent: a malformed event log line, an unknown event type, a seq mismatch on append, an inconsistent active_session pointer, etc. These should not happen in normal operation; treat them as bugs to investigate.
StorageLockedError¶
Bases: StrixStorageError
Another process holds the data_dir lock.
Raised by LocalTransport(data_dir=...) when another process already holds the data_dir lock. Strix is single-writer per data_dir.
StorageVersionError¶
Bases: StrixStorageError
Storage on-disk format_version is unsupported by this SDK version.
Raised when the .strix-storage marker file has a format_version this SDK doesn't support. Will be relevant when post-v1 format migrations land.
StrixBrokerError¶
Bases: StrixError
Base class for broker-adapter-related errors.
Base for broker-adapter-related errors raised by strix.reconcile. Catch this if you want a single handler for any broker-side problem.
BrokerFetchError¶
Bases: StrixBrokerError
A :class:BrokerReadAdapter fetch_* call raised.
The original exception is preserved via __cause__. operation
identifies which adapter method failed: "fetch_executions",
"fetch_open_orders", or "fetch_positions".
Raised by strix.reconcile when a BrokerReadAdapter fetch_* method raises. The original exception is preserved via __cause__.
Attributes:
operation: str— which adapter method failed:"fetch_executions","fetch_open_orders", or"fetch_positions".
BrokerReconciliationError¶
Bases: StrixBrokerError
Raised when :func:strix.reconcile detects mismatches between the
resumed event-log state and the broker's view.
Carries the full lists of mismatches so callers can inspect and resolve.
Executions ingested earlier in the same reconcile call remain applied;
only the mismatch check raised. PositionAdjusted / OrderStatusChanged
events emitted by trust mode before the raise also remain applied — the
raise reports any unresolved mismatches that trust mode would not
auto-correct.
Raised by strix.reconcile when divergences are detected: under on_mismatch="raise" (any mismatch) or on_mismatch="trust" (any unresolved order mismatch — trust mode auto-corrects only the bounded subset, see crash recovery). Executions ingested earlier in the same call remain applied; only the mismatch check raised.
Attributes:
position_mismatches: list[PositionMismatch]order_mismatches: list[OrderMismatch]
Order ID contract¶
The order_id field on Order and Execution is a string with these rules:
- Non-empty.
- At most 128 characters.
- No ASCII control characters (codepoints
< 0x20or= 0x7F).
Anything beyond that — letters, digits, punctuation, non-ASCII Unicode — is accepted. Violations raise ValueError from the Order / Execution constructor.
When order_id is omitted from strix.order(...), the SDK auto-generates a UUIDv7. Pass your own when you need stable IDs for broker reconciliation, replay, or multi-system attribution.
If you supply your own and care about cloud-DB index locality (when CloudTransport lands post-v1), prefer time-ordered schemes — UUIDv7, ULID, Snowflake. Pure UUIDv4 will work but degrades the secondary index when orders move to a hosted DB.
Numeric inputs¶
Wherever Strix accepts a quantity or price (qty, limit_price, price, RiskSettings.max_qty_per_order, RiskSettings.position_limits values), the type is:
Floats are rejected with TypeError. Strings parse via Decimal(str) — anything Decimal accepts is valid ("100", "100.5", "1.5e2", etc.). Non-finite values (NaN, Infinity) are rejected with ValueError.