> For the complete documentation index, see [llms.txt](https://docs.jaawle.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.jaawle.xyz/protocol/trading-engine.md).

# Trading engine

The `PositionManager` runs every position. It is one contract for all markets; each market's pool is the counterparty. Margin is isolated: a position is keyed by `(account, pool, isLong)`, carries its own USDC collateral, and nothing else the account holds backs it. An account can hold one long and one short per market at the same time.

Collateral is USDC and lives in the PositionManager. Size is a USD notional; the position also records the number of shares (or asset units) of exposure at entry, `sizeTokens`, so P\&L and dividends are computed per token.

## Prices and sessions

Every fill reads the `PriceFeed`, which serves Pyth's mid with a bid and ask one confidence interval either side. Longs cross the spread on the way in and out: a long opens at the ask and closes at the bid; a short opens at the bid and closes at the ask. Mark-to-market for unrealised P\&L, liquidation checks and pool exposure uses the mid. There is no price impact: a trade of any size fills at that price, subject to the open-interest caps.

Stock and ETF marks come from Pyth's tokenized-stock feeds, which print around the clock; while the NYSE session is Closed the keeper stops refreshing stock prices, so the on-chain print goes stale and the feed serves the closing mark. Crypto pools price around the clock and are always in the Regular session.

| session  | open a position                  | close, partial close | liquidate, trigger | taker fee               | maintenance                       |
| -------- | -------------------------------- | -------------------- | ------------------ | ----------------------- | --------------------------------- |
| Regular  | yes                              | yes                  | yes                | 1x                      | 1x                                |
| Extended | no (`OpensOnlyInRegularSession`) | yes                  | yes                | x `offHoursFeeMult` (2) | x `offHoursMaintenanceMult` (1.5) |
| Closed   | no                               | no (`SessionClosed`) | detection only     | none                    | x 1.5 for detection               |

The feed's session is pushed by the settler from the NYSE calendar and expires to Closed on its own after `sessionMaxAge` (2 hours) without a push.

## Opening

`increasePosition(pool, isLong, collateralDelta, sizeDeltaUsd, acceptablePrice)`, or `increasePositionFor` from the router for a one-click account. Checks, in order:

1. the market is enabled, the PositionManager is not paused, the account is on the KYC allowlist;
2. the session is Regular;
3. `price = ask` for a long, `bid` for a short; the call reverts `PriceNotAcceptable` if the price is worse than `acceptablePrice`;
4. the market's borrow and funding indices are brought to now;
5. the collateral is pulled in, and any borrow, funding and dividend accrued on an existing position is realised into the collateral so the snapshots can reset (`Undercollateralised` if that would leave nothing);
6. the open fee `sizeDeltaUsd x takerFeeBps` is taken from the collateral and split: `insuranceFeeBps` to the insurance vault, `protocolFeeBps` to the treasury, the rest to the pool;
7. the exposure is booked with the pool: a long calls `reserveInventory(tokens)`, a short calls `increaseShortOi(sizeDeltaUsd)`; either reverts `OiCapExceeded` at its cap;
8. the leverage check: `sizeUsd <= collateral x maxLeverage` on the whole position, after the fee.

```
tokens     = sizeDeltaUsd x stockUnit / price
entryPrice = sizeUsd x stockUnit / sizeTokens          (weighted when adding to a position)
```

A call with `collateralDelta > 0` and `sizeDeltaUsd = 0` only adds margin. The terminal sizes a ticket as margin plus fee plus a small spread buffer so that a "10x" order passes the leverage check after the fee.

## The pool as counterparty

The pool never trades. It backs positions with its balance sheet and settles with the PositionManager in USDC:

* **A long reserves inventory.** On a share-backed pool `reservedStock` may not exceed the shares on hand, so every open long is matched by shares the pool actually owns. On a synthetic pool, which has no shares, the long cap is the same as the short cap expressed in asset units at the mid: `USDC x shortCapBps / mid`.
* **A short consumes the USDC cap.** `shortOiUsd` may not exceed `USDC x shortCapBps` (80 %, 50 % on meme pools). The cap is checked when a short opens; a later fall in the pool's USDC does not force anything closed.
* **Money moves through `settleTrader(to, pnl, tradingFee, borrowFee)`.** Losses, fees and the borrow leg are pulled from the PositionManager into the pool; profits are paid from the pool to the PositionManager, which then pays the trader. The pool records each leg in `yieldTotals`.

The traders' unrealised P\&L and accrued dividend pass-through are a liability (or asset) of the pool in its NAV through `poolExposure`, so LP tokens are priced net of what the pool owes.

## While open

Three things accrue against a position between open and close.

**Unrealised P\&L**, at the mid for display and liquidation checks, at the bid or ask when realised:

```
long  : tokens x price - size
short : size - tokens x price
```

**Borrow**, paid to the pool for the capital the position reserves (inventory for longs, USDC for shorts):

```
utilisation = (long OI + short OI) / NAV            capped at 100 %
cumBorrow  += borrowRatePerSecond x utilisation x dt
owed        = size x (cumBorrow now - cumBorrow at the position's snapshot)
```

`borrowRatePerSecond` is 27.78e-9 in the local deployment, which is 87.6 % per year at full utilisation; at 10 to 20 % utilisation a position pays about 9 to 18 % of its notional per year.

**Funding**, a skew charge in the style of Synthetix: the heavier side pays the pool, the lighter side receives nothing.

```
skew        = |long OI - short OI| / (long OI + short OI)
rate        = maxFundingPerSecond x skew                   on the heavy side only
cumFundingLong or cumFundingShort += rate x dt
owed        = size x (index now - index at the position's snapshot)
```

`maxFundingPerSecond` is 3.17e-9, which is 10 % per year at 100 % skew. Because the pool is the counterparty, whatever the heavy side pays and no light side absorbs accrues to the pool.

The indices advance on every state change in the market (`_updateBorrow`), and the view `getPosition` adds the time since the last update so the terminal shows the live figure. Borrow and funding are settled together as one "borrow owed" leg whenever the position is reduced, closed, liquidated or increased, and that leg is split like the taker fee: 10 % insurance, 10 % treasury, 80 % pool.

**Dividends** (share-backed pools): on an ex-date the pool calls `applyDividend(perShare)`; a long is credited `tokens x perShare`, a short debited the same, realised into collateral at close. See [Dividends](/protocol/dividends.md).

## Closing and partial close

`decreasePosition(pool, isLong, sizeDeltaUsd, collateralDelta, acceptablePrice)`, or `decreasePositionFor` from the router. Allowed in the Regular and Extended sessions. `sizeDeltaUsd` is the notional to close; anything at or above the position's size is a full close.

```
price     = bid (long) or ask (short)
fee       = sizeDeltaUsd x takerFeeBps x (offHoursFeeMult if Extended)
tokens    = sizeTokens x sizeDeltaUsd / sizeUsd
pnl       = P&L on the closed tokens at that price
realised  = pnl + dividend adjustment - borrow owed - fee
remaining = collateral + realised
```

On a **full close** the whole `remaining` is paid to the account and the position is deleted. On a **partial close** the position keeps the reduced size and tokens, its snapshots reset to the current indices, `collateralDelta` (capped at `remaining`) is paid out, the rest stays as collateral, the leverage check runs again on what is left, and any liquidation flag is cleared. The dividend accrual attributed to the closed fraction is released from the market's aggregates pro rata.

The pool side of a close: `settleTrader` pulls the loss and 80 % of the fee and borrow leg, or pays the profit; `releaseInventory` or `decreaseShortOi` frees the cap. The payout goes to the account even when the router or the TriggerBook made the call.

If `remaining` would be negative the trader owes more than the collateral: see [bad debt](/risk/liquidation-and-insurance.md#bad-debt-and-the-insurance-vault).

## Take-profit and stop-loss

The `TriggerBook` stores one take-profit and one stop-loss per position, plus the share of the position to close when either fires (`closeBps`, 10 000 closes everything).

* **Setting.** The position's owner calls `set(pool, isLong, takeProfit, stopLoss, closeBps)`; a one-click subaccount reaches `setFor` through the router's `SetTrigger` action. Zeros clear the trigger. The levels are validated against the same price the trigger will later be checked against, the bid for a long and the ask for a short: a long's take-profit must be above it and its stop-loss below; the reverse for a short. A stop set inside the spread is refused (`BadTrigger`), so it cannot fire on the next tick.
* **Checking.** `check` reports a hit when the bid reaches a long's take-profit or falls to its stop-loss, or when the ask falls to a short's take-profit or rises to its stop-loss. The trigger remembers the position's `openedAt`; if the position was closed and a new one opened since, the trigger is dead and never fires on the new one.
* **Executing.** The keeper calls `execute` as soon as a trigger has crossed. Anyone may `flag` a crossed trigger, and after `priorityWindow` seconds (60 by default, settable by ADMIN) anyone may execute it. Execution deletes the trigger and calls `decreasePositionFor` with no slippage bound, so a stop through a gap fills at whatever the market gives instead of reverting. It is an ordinary close: the Extended-hours fee multiplier applies, nothing executes while the session is Closed, and the payout goes to the account.

The TriggerBook holds the ROUTER role on the PositionManager, which is what lets it close a position it does not own.

## Limit orders

The `LimitBook` holds resting orders to open a position: account, pool, side, collateral, size, limit price and a deadline. The collateral is escrowed in the book from placement.

* **Placing.** The trader calls `place(pool, isLong, collateral, sizeUsd, limitPrice, deadline)`; a one-click subaccount reaches `placeFor` through the router's `PlaceLimit` action, and the router moves the collateral from the account into the book. The account must pass KYC and the deadline must be in the future. A limit that is already reached, a buy at or above the ask or a sell at or below the bid, is refused (`BadOrder`): that is a market order.
* **Checking.** `check` reports the order as reached when the ask falls to a buy's limit or the bid rises to a sell's, and never past the deadline.
* **Executing.** The keeper calls `execute` once an order is reached. Anyone may `flag` a reached order, and after `priorityWindow` seconds anyone may execute it. Execution deletes the order and calls `increasePositionFor` with the limit price as the acceptable price, so the fill is at the market's ask or bid and never worse than the limit. It is an ordinary open: the session, the leverage cap and the open-interest caps apply, and nothing executes while the session is Closed.
* **Cancelling.** The owner calls `cancel` (`cancelFor` through the router); anyone may `expire` an order past its deadline. Both delete the order and return the collateral to the account.

The LimitBook holds the ROUTER role on the PositionManager, which is what lets it open a position for an account.

## Liquidation price

The mark at which a position becomes liquidatable, using the rule in [Liquidation and insurance](/risk/liquidation-and-insurance.md):

```
long  : liq = (size + maintenance + closeFee + borrowOwed - collateral - dividendCredit) / tokens
short : liq = (collateral + size - maintenance - closeFee - borrowOwed + dividendCredit) / tokens

maintenance = size x maintenanceBps (x 1.5 outside the Regular session)
closeFee    = size x takerFeeBps
```

Borrow keeps accruing, so the liquidation price of a long drifts up and of a short down over time.

## Events

`PositionIncreased`, `PositionDecreased` (with realised P\&L, fee and borrow), `PositionLiquidated`, `PositionFlagged`, `FundingAccrued`, `DividendApplied`, `ProtocolFeePaid`, `BadDebt`. The keeper turns the first three into fill rows and candle volume; the chain stays the source of truth.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.jaawle.xyz/protocol/trading-engine.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
