ZenQuant.OrderState (zen_quant v0.2.0)

Copy Markdown View Source

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 :filled or :cancelled status (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

FunctionArityDescriptionParam Kinds
summary1Structured summary of order state for agent consumption.order: value
cancel2Cancel a non-terminal order, preserving any partial fill state.order: value, opts: value
on_fill3Apply a fill event to an order, returning updated state with VWAP.order: value, fill: exchange_data, opts: value
new2Create initial order state from params map.params: value, opts: value

Summary

Types

Order state map tracking lifecycle

Order summary for agent consumption

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

order_state()

@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

summary_result()

@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

cancel(order, opts \\ [])

@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}}
}

new(params, opts \\ [])

@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]
   }}
}

on_fill(order, fill, opts \\ [])

@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}}
}

summary(order)

@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
  }
}