# Closing & Partial Close

Closing does not require oracle data — the keeper supplies the settlement price. All close functions
are on the Diamond.

## Full close

```solidity
function closeTrade(bytes32 positionHash) external;
function closeTrade(bytes32 positionHash, uint24 broker) external;
```

The two-argument form credits the close fee to a broker id. Use `0` or the single-argument form if
you do not operate a broker integration.

::: warning The close broker is independent of the open broker
The broker recorded when the position was opened does **not** carry over. Whatever id you pass here
receives the close-fee share — and the single-argument form credits the default broker. A broker
integration must pass its id on the close as well as the open. See
[Brokers & Referrals](/introduction/brokers#open-and-close-are-credited-separately).
:::

```ts
import { publicClient, walletClient, DIAMOND } from './config'

const CLOSE_ABI = [{
  type: 'function',
  name: 'closeTrade',
  stateMutability: 'nonpayable',
  inputs: [{ name: 'positionHash', type: 'bytes32' }],
  outputs: [],
}] as const

const hash = await walletClient.writeContract({
  address: DIAMOND,
  abi: CLOSE_ABI,
  functionName: 'closeTrade',
  args: [positionHash],
})
await publicClient.waitForTransactionReceipt({ hash })
```

::: warning
Solidity overloads share a name. If you put every `closeTrade` variant into one ABI array, `viem`
cannot tell them apart — pass only the fragment you intend to call, or disambiguate explicitly.
:::

A full close also cancels every [TP/SL order](/onchain/tpsl-orders) attached to that position, each
emitting `DecreaseOrderCancelled`.

## Partial close

```solidity
function closeTrade(bytes32 positionHash, uint128 closeQty, uint24 broker) external;
```

Closes `closeQty` of the position and leaves the rest open. The remainder keeps its entry price, and
accrued funding and holding fees are split proportionally.

```ts
const PARTIAL_CLOSE_ABI = [{
  type: 'function',
  name: 'closeTrade',
  stateMutability: 'nonpayable',
  inputs: [
    { name: 'positionHash', type: 'bytes32' },
    { name: 'closeQty', type: 'uint128' },
    { name: 'broker', type: 'uint24' },
  ],
  outputs: [],
}] as const

// Close half of the position.
await walletClient.writeContract({
  address: DIAMOND,
  abi: PARTIAL_CLOSE_ABI,
  functionName: 'closeTrade',
  args: [positionHash, position.qty / 2n, 0],
})
```

| `closeQty` | Result |
| :--- | :--- |
| `0` | Full close |
| `>= position.qty` | Full close |
| Between | Partial close, remainder stays open |

`closeQty` uses the same 1e10 scale as `qty` — see [Precision & Units](/introduction/precision).

::: info Merged positions only
Partial close requires a position with a deterministic key. Legacy positions created before the
merged-position upgrade are full-close only, and a partial request against one reverts. See
[Positions are merged slots](/introduction/concepts#positions-are-merged-slots).
:::

## Batch close

```solidity
function batchCloseTrade(bytes32[] calldata positionHashes) external;
function batchCloseTrade(bytes32[] calldata positionHashes, uint24 broker) external;
```

```ts
const BATCH_CLOSE_ABI = [{
  type: 'function',
  name: 'batchCloseTrade',
  stateMutability: 'nonpayable',
  inputs: [{ name: 'positionHashes', type: 'bytes32[]' }],
  outputs: [],
}] as const

await walletClient.writeContract({
  address: DIAMOND,
  abi: BATCH_CLOSE_ABI,
  functionName: 'batchCloseTrade',
  args: [positions.map((p) => p.positionHash)],
})
```

## Finding the position hash

Three ways, in order of convenience:

```ts
// 1. Compute it — no RPC call needed.
import { keccak256, encodeAbiParameters } from 'viem'

const positionHash = keccak256(
  encodeAbiParameters(
    [{ type: 'address' }, { type: 'address' }, { type: 'bool' }, { type: 'address' }, { type: 'string' }],
    [user, pairBase, isLong, lvToken, 'position.v1'],
  ),
)

// 2. Read it from the contract — getPositionHash(user, pairBase, isLong, lvToken)
// 3. Read open positions — getPositionsV4(user, pairBase)
```

See [Reading Data](/onchain/reading-data#getpositionhash).

## After sending

Closing is a request. The keeper settles it and emits:

| Event | Meaning |
| :--- | :--- |
| `CloseTradeRequested` | Your transaction was accepted |
| `ClosePosition` | Position fully closed and settled |
| `PositionDecreased` | Partial close settled, remainder still open |
| `ExecuteCloseRejected` | Close could not be settled |

`ClosePosition` and `PositionDecreased` carry a `CloseInfo` with the realized numbers:

```solidity
struct CloseInfo {
    uint128 closePrice;  // 1e18
    int96 fundingFee;    // lvToken decimals, signed
    uint96 closeFee;     // lvToken decimals
    int96 pnl;           // lvToken decimals, signed
    uint96 holdingFee;   // lvToken decimals
}
```

## Common reverts

| Error | Cause |
| :--- | :--- |
| `NonexistentTrade` | Position already closed, or wrong hash |
| `PositionNotCloseable` | Minimum holding period not elapsed — check `earliestCloseTime` |
| `MarketClosed` | Market outside trading hours |
| `CoolingOffPeriod` | Action temporarily unavailable |

Full list: [Error Reference](/reference/errors).
