Immutable order lifecycle tracking — pure functions for order state transitions.
Tracks order creation, partial fills, VWAP accumulation, and cancellation without any exchange coupling. Agents feed fill events and get back updated state maps they can store however they choose.
Terminology
- VWAP - Volume-Weighted Average Price across fills
- Terminal - An order in
:filledor:cancelledstatus (no further transitions)
Status Flow
:pending → :partial → :filled
:pending → :cancelled
:partial → :cancelled (preserves fill history)Example
{:ok, order} = ZenQuant.OrderState.new(%{
id: "order-123", symbol: "BTC/USDT:USDT",
side: :buy, type: :limit, price: 67800.0, quantity: 1.0
})
{:ok, order} = ZenQuant.OrderState.on_fill(order, %{price: 67800.0, quantity: 0.5})
order.status
# => :partial
ZenQuant.OrderState.summary(order)
# => %{id: "order-123", status: :partial, fill_pct: 50.0, ...}API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
summary | 1 | Structured summary of order state for agent consumption. | order: value |
cancel | 2 | Cancel a non-terminal order, preserving any partial fill state. | order: value, opts: value |
on_fill | 3 | Apply a fill event to an order, returning updated state with VWAP. | order: value, fill: exchange_data, opts: value |
new | 2 | Create initial order state from params map. | params: value, opts: value |
Summary
Functions
Cancel a non-terminal order, preserving any partial fill state.
Create initial order state from params map.
Apply a fill event to an order, returning updated state with VWAP.
Structured summary of order state for agent consumption.
Types
@type order_state() :: %{ id: String.t(), symbol: String.t(), side: :buy | :sell, type: :limit | :market | :stop | :stop_limit, price: number() | nil, quantity: float(), status: :pending | :partial | :filled | :cancelled, filled_qty: float(), remaining_qty: float(), avg_price: float(), fills: [map()], created_at: DateTime.t(), updated_at: DateTime.t() }
Order state map tracking lifecycle
@type summary_result() :: %{ id: String.t(), status: atom(), fill_pct: float(), avg_price: float(), fill_count: non_neg_integer(), remaining_qty: float() }
Order summary for agent consumption
Functions
@spec cancel( order_state(), keyword() ) :: {:ok, order_state()} | {:error, atom()}
Cancel a non-terminal order, preserving any partial fill state.
Parameters
order- Current order state map (value)opts- Options: now (DateTime override for testing) (default:[], value)
Returns
{:ok, cancelled_order} or {:error, :already_terminal} (tuple)
Example
{:ok, %{status: :cancelled, remaining_qty: 0.0}}Errors
:already_terminal
# descripex:contract
%{
params: %{
opts: %{
default: [],
description: "Options: now (DateTime override for testing)",
kind: :value
},
order: %{description: "Current order state map", kind: :value}
},
errors: [:already_terminal],
returns: %{
type: :tuple,
description: "{:ok, cancelled_order} or {:error, :already_terminal}"
},
returns_example: {:ok, %{status: :cancelled, remaining_qty: 0.0}}
}
@spec new( map(), keyword() ) :: {:ok, order_state()} | {:error, atom()}
Create initial order state from params map.
Parameters
params- Map with :id, :symbol, :side, :type, :price, :quantity. Sides/types accept strings or atoms. (value)opts- Options: now (DateTime override for testing) (default:[], value)
Returns
{:ok, order_state} or {:error, reason} for validation failures (tuple)
Example
{:ok,
%{
id: "order-123",
status: :pending,
type: :limit,
symbol: "BTC/USDT:USDT",
side: :buy,
price: 67800.0,
quantity: 1.0,
filled_qty: 0.0,
remaining_qty: 1.0,
avg_price: 0.0,
fills: [],
created_at: ~U[2026-02-25 12:00:00Z],
updated_at: ~U[2026-02-25 12:00:00Z]
}}Errors
:missing_required_field:invalid_side:invalid_type:invalid_quantity:missing_price:invalid_price
# descripex:contract
%{
params: %{
opts: %{
default: [],
description: "Options: now (DateTime override for testing)",
kind: :value
},
params: %{
description: "Map with :id, :symbol, :side, :type, :price, :quantity. Sides/types accept strings or atoms.",
kind: :value
}
},
errors: [:missing_required_field, :invalid_side, :invalid_type,
:invalid_quantity, :missing_price, :invalid_price],
returns: %{
type: :tuple,
description: "{:ok, order_state} or {:error, reason} for validation failures"
},
returns_example: {:ok,
%{
id: "order-123",
status: :pending,
type: :limit,
symbol: "BTC/USDT:USDT",
side: :buy,
price: 67800.0,
quantity: 1.0,
filled_qty: 0.0,
remaining_qty: 1.0,
avg_price: 0.0,
fills: [],
created_at: ~U[2026-02-25 12:00:00Z],
updated_at: ~U[2026-02-25 12:00:00Z]
}}
}
@spec on_fill(order_state(), map(), keyword()) :: {:ok, order_state()} | {:error, atom()}
Apply a fill event to an order, returning updated state with VWAP.
Parameters
order- Current order state map from new/2 or previous on_fill/3 (value)fill- Map with :price and :quantity for this fill (exchange_data)opts- Options: now (DateTime override for testing) (default:[], value)
Returns
{:ok, updated_order} with status :partial or :filled, or {:error, :already_terminal | :invalid_fill} (tuple)
Example
{:ok,
%{status: :partial, filled_qty: 0.5, remaining_qty: 0.5, avg_price: 67800.0}}Errors
:already_terminal:invalid_fill
# descripex:contract
%{
params: %{
opts: %{
default: [],
description: "Options: now (DateTime override for testing)",
kind: :value
},
order: %{
description: "Current order state map from new/2 or previous on_fill/3",
kind: :value
},
fill: %{
description: "Map with :price and :quantity for this fill",
source: "WebSocket fill events or fetch_order",
kind: :exchange_data
}
},
errors: [:already_terminal, :invalid_fill],
returns: %{
type: :tuple,
description: "{:ok, updated_order} with status :partial or :filled, or {:error, :already_terminal | :invalid_fill}"
},
returns_example: {:ok,
%{status: :partial, filled_qty: 0.5, remaining_qty: 0.5, avg_price: 67800.0}}
}
@spec summary(order_state()) :: summary_result()
Structured summary of order state for agent consumption.
Parameters
order- Current order state map (value)
Returns
Map with :id, :status, :fill_pct, :avg_price, :fill_count, :remaining_qty (map)
Example
%{
id: "order-123",
status: :partial,
remaining_qty: 0.5,
avg_price: 67800.0,
fill_pct: 50.0,
fill_count: 1
}# descripex:contract
%{
params: %{order: %{description: "Current order state map", kind: :value}},
returns: %{
type: :map,
description: "Map with :id, :status, :fill_pct, :avg_price, :fill_count, :remaining_qty"
},
returns_example: %{
id: "order-123",
status: :partial,
remaining_qty: 0.5,
avg_price: 67800.0,
fill_pct: 50.0,
fill_count: 1
}
}