> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paradex.trade/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paradex.trade/_mcp/server.

# Market Maker Protections (MMP)

> **Note**
>
> Market Maker Protections (MMP) is currently available for a set of whitelisted users.

## Overview

MMP protects market makers from excessive fills during fast markets by automatically freezing protected orders once configured limits are exceeded.

MMP is available for **perps and dated options** and is configured **per account (L2 address) and underlying (base asset)**. For example, one BTC configuration applies across all BTC perps and dated options for that account.

Only order book orders explicitly flagged for MMP are protected. Orders without the MMP flag are unaffected. RFQ and block trade fills count toward MMP limits only if the account opts in. See [RFQ and block trades](#rfq-and-block-trades).

## How MMP works

MMP tracks protected fills over a single sliding time window. Size, delta and vega are measured over the same window and compared independently against their configured limits.

Only **MMP flagged fills** count toward the window, and on the order book only the fill where your order was the resting maker. A flagged order that crosses the book is not counted. [RFQ and block trade](#rfq-and-block-trades) legs follow their own rules.

> **Note**
>
> Because flagged orders that cross the book are not counted, they do not offset your maker fills in the window. Delta and vega limits trip on your gross maker exposure, so hedging with flagged aggressive orders does not keep the window's net delta or vega down.

| Feature             | Behavior                                                                                                                                                      |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Size**            | Gross traded size over the window: the sum of absolute fill sizes.                                                                                            |
| **Delta**           | Absolute value of the net (signed) delta of protected fills.                                                                                                  |
| **Vega**            | Absolute value of the net (signed) vega of protected fills, measured in USD per 1% move in implied volatility.                                                |
| **Trigger**         | A limit trips only when the corresponding window value **exceeds** the configured limit. Matching the limit exactly does not trip MMP.                        |
| **Triggering fill** | The fill that first breaches a limit completes in full. Protection activates immediately afterward.                                                           |
| **Cancellation**    | Resting MMP orders for that underlying are canceled after a trigger. Further protected fills on that underlying are blocked while the cancellations complete. |
| **Cross market**    | Perps and dated options for the same underlying share one MMP state and set of limits.                                                                        |

MMP is enforced by the matching engine, so concurrent fills across markets share the same state. **At most one fill can take exposure beyond a configured limit before protection activates.**

> **Warning**
>
> MMP relies on up to date Greeks. If Greeks become stale or unavailable for any market on an underlying, all MMP orders on that underlying are canceled with reason `MMP_GREEKS_UNAVAILABLE`, including orders on perps. This check applies to MMP orders that can rest, so an MMP `IOC` order is not canceled for stale Greeks.

## RFQ and block trades

RFQ and block trade fills are protected only for accounts that opt in. Set `protect_block_trades` to `true` in the [configuration](#configuration) for a base asset to protect your maker legs on that base asset. RFQ executions are included, because an RFQ settles as a block trade. The option is off by default.

| Rule                | Behavior                                                                                                                                                                                                                                                                                                                         |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Scope**           | Set per account and per base asset, not per quote or per block.                                                                                                                                                                                                                                                                  |
| **Which legs**      | On a block you create directly, only your maker legs are protected. On an offer you submit, your own legs are protected whichever side they end up on, because answering a request for quotes is quoting. `MARKET` legs are never protected. A protected leg counts toward the window whether it is the maker or the taker side. |
| **Requester**       | On a block built from offers, such as an RFQ, the account that requested the quotes is never protected.                                                                                                                                                                                                                          |
| **Shared window**   | Protected block trade legs count toward the same window and limits as your MMP order book fills on that base asset.                                                                                                                                                                                                              |
| **Set at creation** | Protection is decided when the block is created and is not changed afterward. Turning the option off applies only to blocks created afterward; existing blocks keep the protection their legs were created with. Turning it off can take a short time to take effect.                                                            |
| **Visibility**      | A leg's flags are returned only to the account that owns the leg. A counterparty reading the same block cannot see whether your leg is protected, and you cannot see whether theirs is.                                                                                                                                          |

You cannot set the MMP flag on a block trade leg yourself. Paradex applies it based on your `protect_block_trades` setting, to the legs described in **Which legs** above.

Before accepting a block trade request, Paradex checks the MMP settings of the accounts involved. If those settings cannot be read, the request is refused with HTTP 503 rather than proceeding without protection. This can happen even for accounts that have no MMP configuration. Retry the request later. The accounts checked depend on the request:

* **Creating a block:** every account with a maker leg, except the account that requested the quotes on a block built from offers.
* **Creating an offer:** the offering account.

## Portfolio Margin

MMP can reduce the initial margin charged for protected resting orders under [Portfolio Margin](/risk/portfolio-margin).

Without MMP, portfolio margin assumes all resting orders may fill. With MMP, Paradex instead considers the **worst fills that can occur before protection activates: fills up to the size limit, plus one full triggering fill**.

Only the **size limit** is used for margin relief. Delta and vega limits provide execution protection but do not reduce margin. Orders without the MMP flag are unaffected.

### Calculation

For each portfolio margin stress scenario:

1. Select the most damaging protected fills up to the size limit, allowing the last fill before the trigger to be partial, plus one full triggering fill.
2. Add their loss to the position loss and the loss from orders without the MMP flag.
3. The scenario with the largest combined loss sets the requirement.

The selected protected fills may differ across scenarios.

### Example

Assume an account has a short BTC straddle and protected resting BTC call quotes with:

* MMP size limit: **2 BTC**
* Quote size: **0.5 BTC**
* 10 bids, each losing **\$4,000** in the BTC −14% scenario
* 6 asks, each losing **\$1,000** in the BTC +14% scenario

The position itself loses:

* **\$6,000** at BTC −14%
* **\$10,000** at BTC +14%

MMP can allow four 0.5 BTC fills up to the 2 BTC limit, plus one additional 0.5 BTC fill that triggers protection.

Portfolio margin therefore considers at most **2.5 BTC of protected fills per scenario**, selecting whichever five quotes are most damaging.

| Scenario | Position | Protected orders       | Total        |
| -------- | -------- | ---------------------- | ------------ |
| BTC −14% | \$6,000  | 5 × \$4,000 = \$20,000 | **\$26,000** |
| BTC +14% | \$10,000 | 5 × \$1,000 = \$5,000  | \$15,000     |

Note that the position alone loses more in the +14% scenario. The protected bids move the binding scenario to −14%.

Without MMP, all 10 bids would count in the −14% scenario:

**\$6,000 + 10 × \$4,000 = \$46,000**

MMP therefore reduces the scenario loss driving the requirement from **\$46,000 to \$26,000**.

### Notes

* If the size limit is at or above the total protected resting size, the requirement is the same as without MMP.
* Maintenance margin is unchanged because it does not include open orders.

## API

All endpoints are private. `GET /v1/account/mmp` also accepts a [read only token](/api/general-information/api-authentication#readonly-tokens), so you can always check which configurations are still enforced. All other endpoints require a regular (trading) JWT.

Sizes are in base units such as BTC or ETH. Times are in milliseconds.

Configuration changes can return HTTP 503 when market data is temporarily unavailable, and resets can return HTTP 503 while the matching engine is unavailable. Retry the request later.

### Configuration

```
GET    /v1/account/mmp
POST   /v1/account/mmp/{base_asset}
DELETE /v1/account/mmp/{base_asset}
POST   /v1/account/mmp/reset
```

| Endpoint                              | Behavior                                                                                                   |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `GET /v1/account/mmp`                 | List configuration and live status per underlying. Optionally filter by `base_asset`.                      |
| `POST /v1/account/mmp/{base_asset}`   | Create or replace the configuration for one underlying.                                                    |
| `DELETE /v1/account/mmp/{base_asset}` | Disable MMP for one underlying. This is the only way to remove a configuration. Returns no body.           |
| `POST /v1/account/mmp/reset`          | Clear the freeze and trading window for one underlying, or for all underlying assets. See [Reset](#reset). |

`POST /v1/account/mmp/{base_asset}` is a full create or replace: it overwrites any existing configuration. Omitted limits are set to `0`. The exception is `protect_block_trades`: if you omit it, the stored value is kept. Only sending `false` turns block trade protection off. Each limit is optional, but at least one of `size_limit`, `delta_limit` or `vega_limit` must be non zero.

| Field                  | Type                 | Meaning                                                                                                                                                                                                                |
| ---------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `interval_ms`          | integer              | Length of the shared sliding window over which protected fills are aggregated, from `100` to `3600000` ms (1 hour).                                                                                                    |
| `frozen_time_ms`       | integer              | Duration of a freeze after a trigger, from `0` to `3600000` ms (1 hour). `0` means frozen until manually reset.                                                                                                        |
| `size_limit`           | decimal string       | Trips when gross traded size in the window exceeds this value. `0` disables the check. Used for margin relief. Maximum 4 decimal places.                                                                               |
| `delta_limit`          | decimal string       | Trips when absolute net delta in the window exceeds this value. `0` disables the check. Maximum 4 decimal places.                                                                                                      |
| `vega_limit`           | decimal string (USD) | Trips when absolute net vega in the window exceeds this value. `0` disables the check. Maximum 4 decimal places. Cannot be the only limit for an underlying with no dated option markets, because perps carry no vega. |
| `protect_block_trades` | Boolean              | Protects your maker legs on RFQ and block trades for this base asset. Defaults to `false`. If omitted, the stored value is kept. See [RFQ and block trades](#rfq-and-block-trades).                                    |

Invalid configurations, including values outside the allowed ranges and unsupported base assets, are rejected with `MMP_CONFIG_INVALID` (HTTP 400).

Request:

```json
POST /v1/account/mmp/BTC

{
  "interval_ms": 1000,
  "frozen_time_ms": 5000,
  "size_limit": "2",
  "delta_limit": "1.5",
  "vega_limit": "0",
  "protect_block_trades": true
}
```

A successful `POST` or `DELETE` means the configuration has been persisted. The matching engine applies the change shortly afterward. Until it does, live fields in `GET /v1/account/mmp`, including `is_frozen` and the window totals, may be absent.

Example `GET /v1/account/mmp` response once the configuration is live:

```json
{
  "account": "0x1234...abcd",
  "enabled": true,
  "results": [
    {
      "base_asset": "BTC",
      "interval_ms": 1000,
      "frozen_time_ms": 5000,
      "size_limit": "2",
      "delta_limit": "1.5",
      "vega_limit": "0",
      "protect_block_trades": true,
      "is_frozen": false,
      "frozen_until": 0,
      "window_size": "0.5",
      "window_delta": "-0.21",
      "window_vega": "0",
      "updated_at": 1758067200000
    }
  ]
}
```

`account` is the account the configurations belong to, and `enabled` indicates whether the account is enabled for MMP. A configuration listed while `enabled` is `false` is still enforced until it is deleted.

`window_size`, `window_delta` and `window_vega` are the current values accumulated over the same `interval_ms` window.

`frozen_until` is:

* the freeze expiry timestamp when `frozen_time_ms > 0`;
* `0` when frozen until manual reset;
* `0` when not frozen.

Use `is_frozen` to distinguish the last two cases.

Changing the configuration clears the trading window for that underlying. Posting an identical configuration again does not.

> **Warning**
>
> A freeze **survives disabling MMP**. Deleting and adding the configuration again does not clear an active freeze.

Disabling MMP cancels all resting MMP orders for that underlying with reason `MMP_DISABLED`, so no order remains resting without protection.

### Reset

A reset keeps the configuration but clears the freeze and trading window. A successful reset takes effect immediately.

Send `POST /v1/account/mmp/reset` with a `base_asset` to reset one underlying, or with an empty body to reset all underlying assets:

```json
POST /v1/account/mmp/reset

{
  "base_asset": "BTC"
}
```

A successful reset returns the underlying assets that were reset:

```json
{
  "results": [
    {
      "base_asset": "BTC",
      "reset_at": 1758067205000
    }
  ]
}
```

| Condition                                                   | Result                                                 |
| ----------------------------------------------------------- | ------------------------------------------------------ |
| Reset within 1 second of a trigger                          | Rejected with `MMP_MIN_FREEZE_NOT_ELAPSED` (HTTP 400). |
| `base_asset` has no configuration                           | Rejected with `MMP_NOT_CONFIGURED` (HTTP 400).         |
| Reset sent with a read only token                           | Rejected with `INVALID_TOKEN_SCOPE` (HTTP 403).        |
| Reset over the rate limit (see [Rate limits](#rate-limits)) | Rejected with HTTP 429.                                |

A reset of all underlying assets is applied underlying by underlying, and underlying assets without a configuration are skipped. If any underlying is still inside its 1 second lockout, the call returns HTTP 400 even though others may already have been reset. The error `data` lists which underlying assets were reset (`data.reset`) and which were refused (`data.refused`).

Canceled quotes are not restored automatically. The maker must place new quotes.

### Orders

Add `"MMP"` to `flags` on a `LIMIT` order on a perp or dated option market.

The flag is supported on REST, batch and WebSocket order entry. The MMP flag is fixed when the order is placed and is kept when the order is modified. To remove it, cancel and replace the order. You cannot set the flag on block trade legs. Paradex sets it on eligible legs when `protect_block_trades` is on. See [RFQ and block trades](#rfq-and-block-trades).

```json
POST /v1/orders

{
  "market": "BTC-USD-26DEC26-100000-C",
  "side": "SELL",
  "type": "LIMIT",
  "size": "0.5",
  "price": "4150",
  "instruction": "POST_ONLY",
  "flags": ["MMP"]
}
```

| Condition                                                                                    | Result                                                                |
| -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Account is not enabled for MMP                                                               | Rejected with `MMP_NOT_ENABLED` (HTTP 403).                           |
| MMP flag set but no configuration exists for the underlying                                  | Order is accepted and then canceled with reason `MMP_NOT_CONFIGURED`. |
| MMP flag set while the underlying is frozen                                                  | Order is accepted and then canceled with reason `MMP_FROZEN`.         |
| MMP flag set on an order type other than `LIMIT`, an unsupported market or a block trade leg | Rejected with `INVALID_PARAMETER` and an explanatory message.         |

MMP related cancellation reasons:

| Reason                   | When                                                                                                                                                  |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MMP_TRIGGERED`          | Resting MMP orders are canceled because a limit was breached.                                                                                         |
| `MMP_FROZEN`             | An MMP order is canceled because the underlying is frozen.                                                                                            |
| `MMP_DISABLED`           | Resting MMP orders are canceled because MMP was disabled for the underlying, or because MMP is temporarily unavailable, for example during a restart. |
| `MMP_NOT_CONFIGURED`     | An MMP order is canceled because no configuration exists for its underlying.                                                                          |
| `MMP_GREEKS_UNAVAILABLE` | MMP orders on an underlying are canceled because Greeks are stale or unavailable for one of its markets.                                              |

### WebSocket

Private channel `mmp` pushes state changes through the standard private subscription envelope.

Every event includes:

| Field          | Meaning                                                                                                          |
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
| `kind`         | State change type: `TRIGGERED`, `UNFROZEN`, `RESET` or `CONFIG_UPDATED`.                                         |
| `base_asset`   | Underlying whose MMP state changed.                                                                              |
| `account`      | Account associated with the MMP state change.                                                                    |
| `frozen_until` | On `TRIGGERED`, the freeze expiry, or `0` for a freeze that lasts until manual reset. Always `0` on other kinds. |
| `created_at`   | Event timestamp.                                                                                                 |

`TRIGGERED` events also include:

| Field            | Meaning                                                                                                                                                                   |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trip_reason`    | Trigger reason: `SIZE`, `DELTA` or `VEGA` when that limit was exceeded, or `INVALID_FILL` when a fill could not be counted and the underlying was frozen as a precaution. |
| `window_size`    | Gross protected size in the window at the time of the trigger.                                                                                                            |
| `window_delta`   | Net delta of protected fills in the window at the time of the trigger.                                                                                                    |
| `window_vega`    | Net vega of protected fills in the window at the time of the trigger.                                                                                                     |
| `trigger_market` | Market whose fill triggered MMP.                                                                                                                                          |

`CONFIG_UPDATED` events also include:

| Field    | Meaning                                                                                                                                       |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `config` | The updated configuration for the underlying, including `protect_block_trades`. Its `removed` flag is set when the configuration was deleted. |

`frozen_until` on non `TRIGGERED` events does not indicate whether an underlying is frozen. Track the frozen state from `TRIGGERED`, `UNFROZEN` and `RESET` events, or read `is_frozen` from `GET /v1/account/mmp`.

### Error codes

| Code                         | HTTP | Meaning                                                                                                         |
| ---------------------------- | ---- | --------------------------------------------------------------------------------------------------------------- |
| `MMP_NOT_ENABLED`            | 403  | Account is not enabled for MMP.                                                                                 |
| `INVALID_TOKEN_SCOPE`        | 403  | Reset sent with a read only token.                                                                              |
| `MMP_CONFIG_INVALID`         | 400  | Configuration or base asset is invalid.                                                                         |
| `MMP_NOT_CONFIGURED`         | 400  | Reset requested for an underlying with no configuration.                                                        |
| `MMP_MIN_FREEZE_NOT_ELAPSED` | 400  | Reset requested within 1 second of a trigger.                                                                   |
| `INVALID_PARAMETER`          | 400  | MMP flag used on an unsupported order type, market or block trade leg, or another request parameter is invalid. |

Configuration changes return HTTP 503 when market data is temporarily unavailable, and resets return HTTP 503 while the matching engine is unavailable.

### Rate limits

Resets are limited to 60 per minute per account. Like the other [API rate limits](/api/general-information/rate-limits/common-questions), this is a token bucket rather than a fixed window: unused allowance builds up to 60 resets and refills continuously. A shorter burst limit also caps how fast resets can be sent, so spread them out rather than sending them together. A reset over either limit returns HTTP 429.

### SDK support

[`paradex-py`](https://github.com/tradeparadex/paradex-py) 0.7.1 is the first version that supports the `MMP` order flag. Earlier versions cannot parse MMP flagged orders or protected block trade legs, including your own. Upgrade to 0.7.1 or later before placing MMP orders or opting in to block trade protection.