> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nilemarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Position Manager

> Primary trader entry point for opening positions with margin, increasing notional with weighted-average strike, adding/removing margin as rescue or optimization, and routing closures and reductions to SettlementEngine

PositionManager handles the full position lifecycle: open, increase, reduce, and margin adjustments. It is the primary entry point for traders interacting with the protocol.

## Contract Relationships

```mermaid theme={null}
graph LR
    pm["PositionManager"]
    margin["MarginAccounts"]
    oracle["OracleModule"]
    risk["RiskManager"]
    se["SettlementEngine"]
    pm -- "locks margin" --> margin
    pm -- "reads prices" --> oracle
    pm -- "validates caps" --> risk
    se -- "closes positions" --> pm
```

## Position Data Structure

Every position is stored onchain as a `Position` struct (see [Types Reference](/build/types-reference) for full definition):

```solidity theme={null}
struct Position {
    address account;                     // Owner address
    bytes32 pairId;                      // Currency pair identifier (e.g., keccak256("EUR/USD"))
    Side side;                           // LONG or SHORT
    uint256 notional;                    // Position size in USDC (6 decimals)
    uint32 tenorSeconds;                 // Tenor duration in seconds (dynamic registry)
    uint64 openTimestamp;                // Block timestamp when opened
    uint64 fixingTimestamp;              // Maturity date (business day adjusted)
    int256 entryStrike;                  // Forward price at open (18 decimals)
    uint64 entryOracleRoundId;           // Oracle round ID at open
    uint256 imLocked;                    // Locked initial margin (mutable via add/remove)
    uint256 mmThreshold;                 // Maintenance margin liquidation threshold
    PositionStatus status;               // OPEN or CLOSED
    CloseReason closeReason;             // Why it was closed (NONE while open)
    uint64 closeTimestamp;               // When closed (0 while open)
    int256 closePrice;                   // Settlement price at close (18 decimals)
    int256 realizedPnl;                  // Capped PnL used for accounting
    int256 marketPnl;                    // Uncapped true mathematical PnL
    uint16 snapshotImBps;                // IM factor at time of open
    uint16 snapshotMmBps;                // MM factor at time of open
    uint16 snapshotTradingFeeBps;        // Trading fee rate at time of open
    uint16 snapshotLiquidationPenaltyBps; // Liquidation penalty at time of open
    uint256 snapshotOracleFee;           // Oracle fee at time of open
    MarginMode marginMode;               // ISOLATED (M2 only)
}
```

<Note>
  There is no `Tenor` enum. Maturities are stored as raw `uint32 tenorSeconds` values from the dynamic registry on `Config` — `Config.getEnabledTenors()` enumerates them.
</Note>

<Info>
  The `imLocked` field is the only mutable economic field on an open position. It changes when the trader adds or removes margin. All `snapshot*` fields are immutable after position creation.
</Info>

## Read Functions

### getPosition

```solidity theme={null}
function getPosition(uint256 positionId) external view returns (Position memory)
```

Returns the full Position struct for a given ID.

### getOpenPositions

```solidity theme={null}
function getOpenPositions(address account) external view returns (uint256[] memory)
```

Returns all open position IDs for an account.

### Position Queries

| Function                                               | Returns                                                   |
| ------------------------------------------------------ | --------------------------------------------------------- |
| `nextPositionId()`                                     | Next position ID counter                                  |
| `allOpenPositionIds()`                                 | All open position IDs in the system                       |
| `allOpenPositionIdsByPair(pairId)`                     | All open position IDs for a specific pair                 |
| `openPositionCount()`                                  | Total count of open positions                             |
| `openPositionCountByPair(pairId)`                      | Open positions for a specific pair                        |
| `openPositionCountAtFixingTs(pairId, fixingTimestamp)` | Open positions at a (pair, fixing) bucket                 |
| `accountGrossNotional(account)`                        | Sum of open notional for an account (used for cap checks) |
| `getPositionStats(pairId)`                             | `(open, matured, liquidatable)` counts for one pair       |
| `getAllPositionStats()`                                | Same stats fanned out across every registered pair        |

### PnL and Risk

| Function                     | Returns                                                 |
| ---------------------------- | ------------------------------------------------------- |
| `unrealizedPnl(positionId)`  | Unrealized PnL for a single position                    |
| `aggregateUnrealizedPnl()`   | Total unrealized PnL across all positions               |
| `positionEquity(positionId)` | Position equity (allocated collateral + unrealized PnL) |
| `isLiquidatable(positionId)` | Whether position is eligible for liquidation            |

## Write Functions

### openPosition

```solidity theme={null}
function openPosition(OpenPositionParams params) returns (uint256 positionId)
```

Opens a new position. Only allowed in NORMAL mode.

**Validation checks (in order):**

| # | Check                                                                      | Revert Error            |
| - | -------------------------------------------------------------------------- | ----------------------- |
| 1 | Pair must be enabled                                                       | `PairNotEnabled`        |
| 2 | Tenor must be enabled                                                      | `TenorNotEnabled`       |
| 3 | `notional > 0`                                                             | `ZeroAmount`            |
| 4 | `notional >= minPositionNotional` (default 100 USDC)                       | `NotionalTooSmall`      |
| 5 | `margin >= minIM` where `minIM = notional * imFactorBps / 10000`           | `MarginBelowMinimum`    |
| 6 | `margin <= notional`                                                       | `MarginExceedsNotional` |
| 7 | RiskManager caps: per-position, per-account, pool exposure, rate-of-change | Various risk errors     |

### increasePosition

```solidity theme={null}
function increasePosition(uint256 positionId, uint256 additionalNotional)
```

Increases an existing position's notional. Only allowed in NORMAL mode.

**Preconditions:** Owner only, OPEN status, not matured, not liquidatable.

The increase uses a weighted average entry strike:

```
newEntryStrike = (oldNotional * oldStrike + additionalNotional * currentForwardPrice) / newNotional
```

Additional margin is calculated proportionally:

```
additionalMargin = additionalNotional * (imLocked / notional)
```

### addPositionMargin

```solidity theme={null}
function addPositionMargin(uint256 positionId, uint256 amount)
```

Increases locked margin. Owner only, position must be OPEN and not matured.

* `imLocked + amount` must not exceed `notional`
* **Allowed even when liquidatable** (rescue mechanism)
* Allowed in NORMAL, DEGRADED, and REDUCE\_ONLY modes

### removePositionMargin

```solidity theme={null}
function removePositionMargin(uint256 positionId, uint256 amount)
```

Decreases locked margin. Owner only, position must be OPEN and not matured.

* **Not allowed when liquidatable** (reverts `PositionLiquidatable`)
* Remaining margin must be >= `minIM` (using snapshotted IM bps)
* Post-removal equity must stay above the MM threshold
* Requires a forward price read (with oracle fee)

### closePosition (protocol-only)

```solidity theme={null}
function closePosition(
    uint256 positionId,
    CloseReason reason,
    int256 closePrice,
    int256 realizedPnl,
    int256 marketPnl
) external
```

Finalizes a position with a close price and computed PnL. Restricted to addresses authorized via `setProtocolAuthorized` — in practice this is `SettlementEngine`. Trader-facing close paths (early termination, settlement, liquidation) all go through SettlementEngine, which then calls back into this function.

### reducePositionNotional (protocol-only)

```solidity theme={null}
function reducePositionNotional(uint256 positionId, uint256 reductionNotional) external
```

Reduces an open position's notional. Restricted to protocol-authorized callers (SettlementEngine). The trader-facing entry point is `SettlementEngine.reducePosition(...)`, which validates ownership and settles realized PnL on the reduced portion before invoking this.

## Snapshotted Parameters

Six configuration parameters are captured at open and remain immutable:

| Field                           | Purpose                                                    |
| ------------------------------- | ---------------------------------------------------------- |
| `snapshotImBps`                 | Initial margin factor — used for margin removal validation |
| `snapshotMmBps`                 | Maintenance margin factor — used for liquidation threshold |
| `snapshotTradingFeeBps`         | Trading fee rate — used at settlement and close            |
| `snapshotLiquidationPenaltyBps` | Liquidation penalty — used only if liquidated              |
| `snapshotOracleFee`             | Oracle fee per price read                                  |
| `marginMode`                    | Margin mode (ISOLATED in M2)                               |

## Events

| Event                   | When Emitted                           |
| ----------------------- | -------------------------------------- |
| `PositionOpened`        | New position created                   |
| `PositionIncreased`     | Position notional increased            |
| `PositionMarginAdded`   | Margin added to position               |
| `PositionMarginRemoved` | Margin removed from position           |
| `PositionClosed`        | Position closed (any close reason)     |
| `TradingFeeCollected`   | Trading fee charged at open / increase |
| `OracleFeeCollected`    | Oracle fee charged for a price read    |
| `ProtocolAuthorizedSet` | `setProtocolAuthorized` toggled        |
| `Swept`                 | Token swept by owner                   |

> Reduction events are emitted by `SettlementEngine` as `PositionReduced`, since reductions are routed through that contract.

## Admin Functions

### setProtocolAuthorized / setRiskManager

```solidity theme={null}
function setProtocolAuthorized(address account, bool authorized) external
function setRiskManager(address _riskManager) external
```

Owner-only. `setProtocolAuthorized` toggles which contracts may call `closePosition` / `reducePositionNotional` (in practice, SettlementEngine). `setRiskManager` re-points the risk module — pass `address(0)` to skip cap checks (intended only for emergency unwind, never production).

### sweep

```solidity theme={null}
function sweep(address token, address recipient, uint256 amount) external
```

Owner-only. Sweeps an arbitrary ERC-20 balance held by the contract.

## Related Pages

<Columns cols={3}>
  <Card title="Position Lifecycle" icon="arrows-spin" href="/protocol/position-lifecycle">
    Product-level overview of the position lifecycle.
  </Card>

  <Card title="Settlement Engine" icon="gavel" href="/build/settlement-engine">
    Settlement, liquidation, and early termination contract.
  </Card>

  <Card title="Margin Accounts" icon="wallet" href="/build/margin-accounts">
    Collateral management contract.
  </Card>
</Columns>
