# `ZenQuant.OrderState`
[🔗](https://github.com/ZenHive/zen_quant/blob/v0.8.1/lib/zen_quant/order_state.ex#L1)

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
| 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` |

# `order_state`

```elixir
@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`

```elixir
@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

# `cancel`

```elixir
@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

```elixir
{:ok, %{status: :cancelled, remaining_qty: 0.0}}
```

## Errors

  * `:already_terminal`

```elixir
# 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`

```elixir
@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

```elixir
{:ok,
 %{
   id: "order-123",
   status: :pending,
   type: :limit,
   symbol: "BTC/USDT:USDT",
   price: 67800.0,
   side: :buy,
   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`

```elixir
# 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",
     price: 67800.0,
     side: :buy,
     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`

```elixir
@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

```elixir
{:ok,
 %{status: :partial, filled_qty: 0.5, remaining_qty: 0.5, avg_price: 67800.0}}
```

## Errors

  * `:already_terminal`
  * `:invalid_fill`

```elixir
# 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`

```elixir
@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

```elixir
%{
  id: "order-123",
  status: :partial,
  remaining_qty: 0.5,
  avg_price: 67800.0,
  fill_pct: 50.0,
  fill_count: 1
}
```

```elixir
# 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
  }
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
